2 * Copyright (c) 2014 by David I. Bell
3 * Permission is granted to use, distribute, or modify this source,
4 * provided that this copyright notice remains intact.
6 * The "gzip" and "gunzip" built-in commands.
7 * These commands are optionally built into sash.
8 * This uses the zlib library by Jean-loup Gailly to compress and
9 * uncompress the files.
15 #include <sys/types.h>
23 #define TGZ_EXT ".tgz"
25 #define TAR_EXT ".tar"
30 * Tables of conversions to make to file extensions.
39 static const CONVERT gzipConvertTable[] =
46 static const CONVERT gunzipConvertTable[] =
56 * Local routines to compress and uncompress files.
58 static BOOL gzip(const char * inputFile, const char * outputFile);
59 static BOOL gunzip(const char * inputFile, const char * outputFile);
61 static const char * convertName
62 (const CONVERT * table, const char * inFile);
66 do_gzip(int argc, const char ** argv)
79 * Look for the -o option if it is present.
80 * If present, it must be at the end of the command.
81 * Remember the output path and remove it if found.
85 if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) &&
86 (argv[argc - 1][0] != '-'))
89 outPath = argv[argc + 1];
93 * Now make sure that there are no more options.
95 for (i = 0; i < argc; i++)
97 if (argv[i][0] == '-')
99 if (strcmp(argv[i], "-o") == 0)
100 fprintf(stderr, "Illegal use of -o\n");
102 fprintf(stderr, "Illegal option\n");
109 * If there is no output path specified, then compress each of
110 * the input files in place using their full paths. The input
111 * file names are then deleted.
115 while (!intFlag && (argc-- > 0))
119 outFile = convertName(gzipConvertTable, inFile);
122 * Try to compress the file.
124 if (!gzip(inFile, outFile))
132 * This was successful.
133 * Try to delete the original file now.
135 if (unlink(inFile) < 0)
137 fprintf(stderr, "%s: %s\n", inFile,
138 "Compressed ok but unlink failed");
148 * There is an output path specified.
149 * If it is not a directory, then either compress the single
150 * specified input file to the exactly specified output path,
153 if (!isDirectory(outPath))
156 r = !gzip(*argv, outPath);
159 fprintf(stderr, "Exactly one input file is required\n");
167 * There was an output directory specified.
168 * Compress each of the input files into the specified
169 * output directory, converting their extensions if possible.
171 while (!intFlag && (argc-- > 0))
176 * Strip the path off of the input file name to make
177 * the beginnings of the output file name.
179 outFile = strrchr(inFile, '/');
187 * Convert the extension of the output file name if possible.
188 * If we can't, then that is ok.
190 outFile = convertName(gzipConvertTable, outFile);
193 * Now build the output path name by prefixing it with
194 * the output directory.
196 outFile = buildName(outPath, outFile);
199 * Compress the input file without deleting the input file.
201 if (!gzip(inFile, outFile))
210 do_gunzip(int argc, const char ** argv)
212 const char * outPath;
214 const char * outFile;
223 * Look for the -o option if it is present.
224 * If present, it must be at the end of the command.
225 * Remember the output path and remove it if found.
229 if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) &&
230 (argv[argc - 1][0] != '-'))
233 outPath = argv[argc + 1];
237 * Now make sure that there are no more options.
239 for (i = 0; i < argc; i++)
241 if (argv[i][0] == '-')
243 if (strcmp(argv[i], "-o") == 0)
244 fprintf(stderr, "Illegal use of -o\n");
246 fprintf(stderr, "Illegal option\n");
253 * If there is no output path specified, then uncompress each of
254 * the input files in place using their full paths. They must
255 * have one of the proper compression extensions which is converted.
256 * The input file names are then deleted.
260 while (!intFlag && (argc-- > 0))
264 outFile = convertName(gunzipConvertTable, inFile);
266 if (inFile == outFile)
268 fprintf(stderr, "%s: %s\n", inFile,
269 "missing compression extension");
276 * Try to uncompress the file.
278 if (!gunzip(inFile, outFile))
285 * This was successful.
286 * Try to delete the original file now.
288 if (unlink(inFile) < 0)
290 fprintf(stderr, "%s: %s\n", inFile,
291 "Uncompressed ok but unlink failed");
300 * There is an output path specified.
301 * If the output path is a device file then uncompress each of
302 * the input files to the device file.
304 if (isDevice(outPath))
306 while (!intFlag && (argc-- > 0))
308 if (!gunzip(*argv++, outPath))
316 * If the output path is not a directory then either uncompress the
317 * single specified input file to the exactly specified output path,
320 if (!isDirectory(outPath))
323 return !gunzip(*argv, outPath);
325 fprintf(stderr, "Exactly one input file is required\n");
331 * There was an output directory specified.
332 * Uncompress each of the input files into the specified
333 * output directory, converting their extensions if possible.
335 while (!intFlag && (argc-- > 0))
340 * Strip the path off of the input file name to make
341 * the beginnings of the output file name.
343 outFile = strrchr(inFile, '/');
351 * Convert the extension of the output file name if possible.
352 * If we can't, then that is ok.
354 outFile = convertName(gunzipConvertTable, outFile);
357 * Now build the output path name by prefixing it with
358 * the output directory.
360 outFile = buildName(outPath, outFile);
363 * Uncompress the input file without deleting the input file.
365 if (!gunzip(inFile, outFile))
374 * Compress the specified input file to produce the output file.
375 * Returns TRUE if successful.
378 gzip(const char * inputFileName, const char * outputFileName)
384 struct stat statBuf1;
385 struct stat statBuf2;
392 * See if the output file is the same as the input file.
393 * If so, complain about it.
395 if (stat(inputFileName, &statBuf1) < 0)
397 perror(inputFileName);
402 if (stat(outputFileName, &statBuf2) < 0)
404 statBuf2.st_ino = -1;
405 statBuf2.st_dev = -1;
408 if ((statBuf1.st_dev == statBuf2.st_dev) &&
409 (statBuf1.st_ino == statBuf2.st_ino))
412 "Cannot compress file \"%s\" on top of itself\n",
419 * Open the input file.
421 inFD = open(inputFileName, O_RDONLY);
425 perror(inputFileName);
431 * Ask the zlib library to open the output file.
433 outGZ = gzopen(outputFileName, "wb9");
437 fprintf(stderr, "%s: gzopen failed\n", outputFileName);
443 * Read the uncompressed data from the input file and write
444 * the compressed data to the output file.
446 while ((len = read(inFD, buf, sizeof(buf))) > 0)
448 if (gzwrite(outGZ, buf, len) != len)
450 fprintf(stderr, "%s: %s\n", inputFileName,
451 gzerror(outGZ, &err));
462 perror(inputFileName);
468 * All done, close the files.
472 perror(inputFileName);
479 if (gzclose(outGZ) != Z_OK)
481 fprintf(stderr, "%s: gzclose failed\n", outputFileName);
495 * Here on an error, to clean up.
502 (void) gzclose(outGZ);
509 * Uncompress the input file to produce the output file.
510 * Returns TRUE if successful.
513 gunzip(const char * inputFileName, const char * outputFileName)
519 struct stat statBuf1;
520 struct stat statBuf2;
527 * See if the output file is the same as the input file.
528 * If so, complain about it.
530 if (stat(inputFileName, &statBuf1) < 0)
532 perror(inputFileName);
537 if (stat(outputFileName, &statBuf2) < 0)
539 statBuf2.st_ino = -1;
540 statBuf2.st_dev = -1;
543 if ((statBuf1.st_dev == statBuf2.st_dev) &&
544 (statBuf1.st_ino == statBuf2.st_ino))
547 "Cannot uncompress file \"%s\" on top of itself\n",
554 * Ask the zlib library to open the input file.
556 inGZ = gzopen(inputFileName, "rb");
560 fprintf(stderr, "%s: gzopen failed\n", inputFileName);
566 * Create the output file.
568 if (isDevice(outputFileName))
569 outFD = open(outputFileName, O_WRONLY);
571 outFD = open(outputFileName, O_WRONLY|O_CREAT|O_TRUNC, 0666);
575 perror(outputFileName);
581 * Read the compressed data from the input file and write
582 * the uncompressed data extracted from it to the output file.
584 while ((len = gzread(inGZ, buf, sizeof(buf))) > 0)
586 if (fullWrite(outFD, buf, len) < 0)
588 perror(outputFileName);
599 fprintf(stderr, "%s: %s\n", inputFileName,
600 gzerror(inGZ, &err));
606 * All done, close the files.
610 perror(outputFileName);
617 if (gzclose(inGZ) != Z_OK)
619 fprintf(stderr, "%s: gzclose failed\n", inputFileName);
633 * Here on an error, to clean up.
640 (void) gzclose(inGZ);
647 * Convert an input file name to an output file name according to
648 * the specified convertion table. The returned name points into a
649 * static buffer which is overwritten on each call. The table must
650 * end in an entry which always specifies a successful conversion.
651 * If no conversion is done the original input file name pointer is
655 convertName(const CONVERT * table, const char * inputFile)
659 static char buf[PATH_LEN];
661 inputLength = strlen(inputFile);
665 testLength = strlen(table->input);
667 if ((inputLength >= testLength) &&
668 (memcmp(table->input,
669 inputFile + inputLength - testLength,
679 * If no conversion was done, return the original pointer.
681 if ((testLength == 0) && (table->output[0] == '\0'))
685 * Build the new name and return it.
687 memcpy(buf, inputFile, inputLength - testLength);
689 memcpy(buf + inputLength - testLength, table->output,
690 strlen(table->output) + 1);