diff options
Diffstat (limited to 'src/sash/cmd_gzip.c')
-rw-r--r-- | src/sash/cmd_gzip.c | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/src/sash/cmd_gzip.c b/src/sash/cmd_gzip.c new file mode 100644 index 0000000..6bcec11 --- /dev/null +++ b/src/sash/cmd_gzip.c @@ -0,0 +1,698 @@ +/* + * Copyright (c) 2014 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * + * The "gzip" and "gunzip" built-in commands. + * These commands are optionally built into sash. + * This uses the zlib library by Jean-loup Gailly to compress and + * uncompress the files. + */ + +#if HAVE_GZIP + + +#include <sys/types.h> +#include <sys/stat.h> +#include <zlib.h> + +#include "sash.h" + + +#define GZ_EXT ".gz" +#define TGZ_EXT ".tgz" +#define Z_EXT ".Z" +#define TAR_EXT ".tar" +#define NO_EXT "" + + +/* + * Tables of conversions to make to file extensions. + */ +typedef struct +{ + const char * input; + const char * output; +} CONVERT; + + +static const CONVERT gzipConvertTable[] = +{ + {TAR_EXT, TGZ_EXT}, + {NO_EXT, GZ_EXT} +}; + + +static const CONVERT gunzipConvertTable[] = +{ + {TGZ_EXT, TAR_EXT}, + {GZ_EXT, NO_EXT}, + {Z_EXT, NO_EXT}, + {NO_EXT, NO_EXT} +}; + + +/* + * Local routines to compress and uncompress files. + */ +static BOOL gzip(const char * inputFile, const char * outputFile); +static BOOL gunzip(const char * inputFile, const char * outputFile); + +static const char * convertName + (const CONVERT * table, const char * inFile); + + +int +do_gzip(int argc, const char ** argv) +{ + const char * outPath; + const char * inFile; + const char * outFile; + int i; + int r; + + r = 0; + argc--; + argv++; + + /* + * Look for the -o option if it is present. + * If present, it must be at the end of the command. + * Remember the output path and remove it if found. + */ + outPath = NULL; + + if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) && + (argv[argc - 1][0] != '-')) + { + argc -= 2; + outPath = argv[argc + 1]; + } + + /* + * Now make sure that there are no more options. + */ + for (i = 0; i < argc; i++) + { + if (argv[i][0] == '-') + { + if (strcmp(argv[i], "-o") == 0) + fprintf(stderr, "Illegal use of -o\n"); + else + fprintf(stderr, "Illegal option\n"); + + return 1; + } + } + + /* + * If there is no output path specified, then compress each of + * the input files in place using their full paths. The input + * file names are then deleted. + */ + if (outPath == NULL) + { + while (!intFlag && (argc-- > 0)) + { + inFile = *argv++; + + outFile = convertName(gzipConvertTable, inFile); + + /* + * Try to compress the file. + */ + if (!gzip(inFile, outFile)) + { + r = 1; + + continue; + } + + /* + * This was successful. + * Try to delete the original file now. + */ + if (unlink(inFile) < 0) + { + fprintf(stderr, "%s: %s\n", inFile, + "Compressed ok but unlink failed"); + + r = 1; + } + } + + return r; + } + + /* + * There is an output path specified. + * If it is not a directory, then either compress the single + * specified input file to the exactly specified output path, + * or else complain. + */ + if (!isDirectory(outPath)) + { + if (argc == 1) + r = !gzip(*argv, outPath); + else + { + fprintf(stderr, "Exactly one input file is required\n"); + r = 1; + } + + return r; + } + + /* + * There was an output directory specified. + * Compress each of the input files into the specified + * output directory, converting their extensions if possible. + */ + while (!intFlag && (argc-- > 0)) + { + inFile = *argv++; + + /* + * Strip the path off of the input file name to make + * the beginnings of the output file name. + */ + outFile = strrchr(inFile, '/'); + + if (outFile) + outFile++; + else + outFile = inFile; + + /* + * Convert the extension of the output file name if possible. + * If we can't, then that is ok. + */ + outFile = convertName(gzipConvertTable, outFile); + + /* + * Now build the output path name by prefixing it with + * the output directory. + */ + outFile = buildName(outPath, outFile); + + /* + * Compress the input file without deleting the input file. + */ + if (!gzip(inFile, outFile)) + r = 1; + } + + return r; +} + + +int +do_gunzip(int argc, const char ** argv) +{ + const char * outPath; + const char * inFile; + const char * outFile; + int i; + int r; + + r = 0; + argc--; + argv++; + + /* + * Look for the -o option if it is present. + * If present, it must be at the end of the command. + * Remember the output path and remove it if found. + */ + outPath = NULL; + + if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) && + (argv[argc - 1][0] != '-')) + { + argc -= 2; + outPath = argv[argc + 1]; + } + + /* + * Now make sure that there are no more options. + */ + for (i = 0; i < argc; i++) + { + if (argv[i][0] == '-') + { + if (strcmp(argv[i], "-o") == 0) + fprintf(stderr, "Illegal use of -o\n"); + else + fprintf(stderr, "Illegal option\n"); + + return 1; + } + } + + /* + * If there is no output path specified, then uncompress each of + * the input files in place using their full paths. They must + * have one of the proper compression extensions which is converted. + * The input file names are then deleted. + */ + if (outPath == NULL) + { + while (!intFlag && (argc-- > 0)) + { + inFile = *argv++; + + outFile = convertName(gunzipConvertTable, inFile); + + if (inFile == outFile) + { + fprintf(stderr, "%s: %s\n", inFile, + "missing compression extension"); + r = 1; + + continue; + } + + /* + * Try to uncompress the file. + */ + if (!gunzip(inFile, outFile)) + { + r = 1; + continue; + } + + /* + * This was successful. + * Try to delete the original file now. + */ + if (unlink(inFile) < 0) + { + fprintf(stderr, "%s: %s\n", inFile, + "Uncompressed ok but unlink failed"); + r = 1; + } + } + + return r; + } + + /* + * There is an output path specified. + * If the output path is a device file then uncompress each of + * the input files to the device file. + */ + if (isDevice(outPath)) + { + while (!intFlag && (argc-- > 0)) + { + if (!gunzip(*argv++, outPath)) + r = 1; + } + + return r; + } + + /* + * If the output path is not a directory then either uncompress the + * single specified input file to the exactly specified output path, + * or else complain. + */ + if (!isDirectory(outPath)) + { + if (argc == 1) + return !gunzip(*argv, outPath); + else + fprintf(stderr, "Exactly one input file is required\n"); + + return 1; + } + + /* + * There was an output directory specified. + * Uncompress each of the input files into the specified + * output directory, converting their extensions if possible. + */ + while (!intFlag && (argc-- > 0)) + { + inFile = *argv++; + + /* + * Strip the path off of the input file name to make + * the beginnings of the output file name. + */ + outFile = strrchr(inFile, '/'); + + if (outFile) + outFile++; + else + outFile = inFile; + + /* + * Convert the extension of the output file name if possible. + * If we can't, then that is ok. + */ + outFile = convertName(gunzipConvertTable, outFile); + + /* + * Now build the output path name by prefixing it with + * the output directory. + */ + outFile = buildName(outPath, outFile); + + /* + * Uncompress the input file without deleting the input file. + */ + if (!gunzip(inFile, outFile)) + r = 1; + } + + return r; +} + + +/* + * Compress the specified input file to produce the output file. + * Returns TRUE if successful. + */ +static BOOL +gzip(const char * inputFileName, const char * outputFileName) +{ + gzFile outGZ; + int inFD; + int len; + int err; + struct stat statBuf1; + struct stat statBuf2; + char buf[BUF_SIZE]; + + outGZ = NULL; + inFD = -1; + + /* + * See if the output file is the same as the input file. + * If so, complain about it. + */ + if (stat(inputFileName, &statBuf1) < 0) + { + perror(inputFileName); + + return FALSE; + } + + if (stat(outputFileName, &statBuf2) < 0) + { + statBuf2.st_ino = -1; + statBuf2.st_dev = -1; + } + + if ((statBuf1.st_dev == statBuf2.st_dev) && + (statBuf1.st_ino == statBuf2.st_ino)) + { + fprintf(stderr, + "Cannot compress file \"%s\" on top of itself\n", + inputFileName); + + return FALSE; + } + + /* + * Open the input file. + */ + inFD = open(inputFileName, O_RDONLY); + + if (inFD < 0) + { + perror(inputFileName); + + goto failed; + } + + /* + * Ask the zlib library to open the output file. + */ + outGZ = gzopen(outputFileName, "wb9"); + + if (outGZ == NULL) + { + fprintf(stderr, "%s: gzopen failed\n", outputFileName); + + goto failed; + } + + /* + * Read the uncompressed data from the input file and write + * the compressed data to the output file. + */ + while ((len = read(inFD, buf, sizeof(buf))) > 0) + { + if (gzwrite(outGZ, buf, len) != len) + { + fprintf(stderr, "%s: %s\n", inputFileName, + gzerror(outGZ, &err)); + + goto failed; + } + + if (intFlag) + goto failed; + } + + if (len < 0) + { + perror(inputFileName); + + goto failed; + } + + /* + * All done, close the files. + */ + if (close(inFD)) + { + perror(inputFileName); + + goto failed; + } + + inFD = -1; + + if (gzclose(outGZ) != Z_OK) + { + fprintf(stderr, "%s: gzclose failed\n", outputFileName); + + goto failed; + } + + outGZ = NULL; + + /* + * Success. + */ + return TRUE; + + +/* + * Here on an error, to clean up. + */ +failed: + if (inFD >= 0) + (void) close(inFD); + + if (outGZ != NULL) + (void) gzclose(outGZ); + + return FALSE; +} + + +/* + * Uncompress the input file to produce the output file. + * Returns TRUE if successful. + */ +static BOOL +gunzip(const char * inputFileName, const char * outputFileName) +{ + gzFile inGZ; + int outFD; + int len; + int err; + struct stat statBuf1; + struct stat statBuf2; + char buf[BUF_SIZE]; + + inGZ = NULL; + outFD = -1; + + /* + * See if the output file is the same as the input file. + * If so, complain about it. + */ + if (stat(inputFileName, &statBuf1) < 0) + { + perror(inputFileName); + + return FALSE; + } + + if (stat(outputFileName, &statBuf2) < 0) + { + statBuf2.st_ino = -1; + statBuf2.st_dev = -1; + } + + if ((statBuf1.st_dev == statBuf2.st_dev) && + (statBuf1.st_ino == statBuf2.st_ino)) + { + fprintf(stderr, + "Cannot uncompress file \"%s\" on top of itself\n", + inputFileName); + + return FALSE; + } + + /* + * Ask the zlib library to open the input file. + */ + inGZ = gzopen(inputFileName, "rb"); + + if (inGZ == NULL) + { + fprintf(stderr, "%s: gzopen failed\n", inputFileName); + + return FALSE; + } + + /* + * Create the output file. + */ + if (isDevice(outputFileName)) + outFD = open(outputFileName, O_WRONLY); + else + outFD = open(outputFileName, O_WRONLY|O_CREAT|O_TRUNC, 0666); + + if (outFD < 0) + { + perror(outputFileName); + + goto failed; + } + + /* + * Read the compressed data from the input file and write + * the uncompressed data extracted from it to the output file. + */ + while ((len = gzread(inGZ, buf, sizeof(buf))) > 0) + { + if (fullWrite(outFD, buf, len) < 0) + { + perror(outputFileName); + + goto failed; + } + + if (intFlag) + goto failed; + } + + if (len < 0) + { + fprintf(stderr, "%s: %s\n", inputFileName, + gzerror(inGZ, &err)); + + goto failed; + } + + /* + * All done, close the files. + */ + if (close(outFD)) + { + perror(outputFileName); + + goto failed; + } + + outFD = -1; + + if (gzclose(inGZ) != Z_OK) + { + fprintf(stderr, "%s: gzclose failed\n", inputFileName); + + goto failed; + } + + inGZ = NULL; + + /* + * Success. + */ + return TRUE; + + +/* + * Here on an error, to clean up. + */ +failed: + if (outFD >= 0) + (void) close(outFD); + + if (inGZ != NULL) + (void) gzclose(inGZ); + + return FALSE; +} + + +/* + * Convert an input file name to an output file name according to + * the specified convertion table. The returned name points into a + * static buffer which is overwritten on each call. The table must + * end in an entry which always specifies a successful conversion. + * If no conversion is done the original input file name pointer is + * returned. + */ +const char * +convertName(const CONVERT * table, const char * inputFile) +{ + int inputLength; + int testLength; + static char buf[PATH_LEN]; + + inputLength = strlen(inputFile); + + for (;;) + { + testLength = strlen(table->input); + + if ((inputLength >= testLength) && + (memcmp(table->input, + inputFile + inputLength - testLength, + testLength) == 0)) + { + break; + } + + table++; + } + + /* + * If no conversion was done, return the original pointer. + */ + if ((testLength == 0) && (table->output[0] == '\0')) + return inputFile; + + /* + * Build the new name and return it. + */ + memcpy(buf, inputFile, inputLength - testLength); + + memcpy(buf + inputLength - testLength, table->output, + strlen(table->output) + 1); + + return buf; +} + + +#endif + +/* END CODE */ |