1 /*
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.
5 *
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.
10 */
11
12 #if HAVE_GZIP
13
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <zlib.h>
18
19 #include "sash.h"
20
21
22 #define GZ_EXT ".gz"
23 #define TGZ_EXT ".tgz"
24 #define Z_EXT ".Z"
25 #define TAR_EXT ".tar"
26 #define NO_EXT ""
27
28
29 /*
30 * Tables of conversions to make to file extensions.
31 */
32 typedef struct
33 {
34 const char * input;
35 const char * output;
36 } CONVERT;
37
38
39 static const CONVERT gzipConvertTable[] =
40 {
41 {TAR_EXT, TGZ_EXT},
42 {NO_EXT, GZ_EXT}
43 };
44
45
46 static const CONVERT gunzipConvertTable[] =
47 {
48 {TGZ_EXT, TAR_EXT},
49 {GZ_EXT, NO_EXT},
50 {Z_EXT, NO_EXT},
51 {NO_EXT, NO_EXT}
52 };
53
54
55 /*
56 * Local routines to compress and uncompress files.
57 */
58 static BOOL gzip(const char * inputFile, const char * outputFile);
59 static BOOL gunzip(const char * inputFile, const char * outputFile);
60
61 static const char * convertName
62 (const CONVERT * table, const char * inFile);
63
64
65 int
do_gzip(int argc,const char ** argv)66 do_gzip(int argc, const char ** argv)
67 {
68 const char * outPath;
69 const char * inFile;
70 const char * outFile;
71 int i;
72 int r;
73
74 r = 0;
75 argc--;
76 argv++;
77
78 /*
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.
82 */
83 outPath = NULL;
84
85 if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) &&
86 (argv[argc - 1][0] != '-'))
87 {
88 argc -= 2;
89 outPath = argv[argc + 1];
90 }
91
92 /*
93 * Now make sure that there are no more options.
94 */
95 for (i = 0; i < argc; i++)
96 {
97 if (argv[i][0] == '-')
98 {
99 if (strcmp(argv[i], "-o") == 0)
100 fprintf(stderr, "Illegal use of -o\n");
101 else
102 fprintf(stderr, "Illegal option\n");
103
104 return 1;
105 }
106 }
107
108 /*
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.
112 */
113 if (outPath == NULL)
114 {
115 while (!intFlag && (argc-- > 0))
116 {
117 inFile = *argv++;
118
119 outFile = convertName(gzipConvertTable, inFile);
120
121 /*
122 * Try to compress the file.
123 */
124 if (!gzip(inFile, outFile))
125 {
126 r = 1;
127
128 continue;
129 }
130
131 /*
132 * This was successful.
133 * Try to delete the original file now.
134 */
135 if (unlink(inFile) < 0)
136 {
137 fprintf(stderr, "%s: %s\n", inFile,
138 "Compressed ok but unlink failed");
139
140 r = 1;
141 }
142 }
143
144 return r;
145 }
146
147 /*
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,
151 * or else complain.
152 */
153 if (!isDirectory(outPath))
154 {
155 if (argc == 1)
156 r = !gzip(*argv, outPath);
157 else
158 {
159 fprintf(stderr, "Exactly one input file is required\n");
160 r = 1;
161 }
162
163 return r;
164 }
165
166 /*
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.
170 */
171 while (!intFlag && (argc-- > 0))
172 {
173 inFile = *argv++;
174
175 /*
176 * Strip the path off of the input file name to make
177 * the beginnings of the output file name.
178 */
179 outFile = strrchr(inFile, '/');
180
181 if (outFile)
182 outFile++;
183 else
184 outFile = inFile;
185
186 /*
187 * Convert the extension of the output file name if possible.
188 * If we can't, then that is ok.
189 */
190 outFile = convertName(gzipConvertTable, outFile);
191
192 /*
193 * Now build the output path name by prefixing it with
194 * the output directory.
195 */
196 outFile = buildName(outPath, outFile);
197
198 /*
199 * Compress the input file without deleting the input file.
200 */
201 if (!gzip(inFile, outFile))
202 r = 1;
203 }
204
205 return r;
206 }
207
208
209 int
do_gunzip(int argc,const char ** argv)210 do_gunzip(int argc, const char ** argv)
211 {
212 const char * outPath;
213 const char * inFile;
214 const char * outFile;
215 int i;
216 int r;
217
218 r = 0;
219 argc--;
220 argv++;
221
222 /*
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.
226 */
227 outPath = NULL;
228
229 if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) &&
230 (argv[argc - 1][0] != '-'))
231 {
232 argc -= 2;
233 outPath = argv[argc + 1];
234 }
235
236 /*
237 * Now make sure that there are no more options.
238 */
239 for (i = 0; i < argc; i++)
240 {
241 if (argv[i][0] == '-')
242 {
243 if (strcmp(argv[i], "-o") == 0)
244 fprintf(stderr, "Illegal use of -o\n");
245 else
246 fprintf(stderr, "Illegal option\n");
247
248 return 1;
249 }
250 }
251
252 /*
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.
257 */
258 if (outPath == NULL)
259 {
260 while (!intFlag && (argc-- > 0))
261 {
262 inFile = *argv++;
263
264 outFile = convertName(gunzipConvertTable, inFile);
265
266 if (inFile == outFile)
267 {
268 fprintf(stderr, "%s: %s\n", inFile,
269 "missing compression extension");
270 r = 1;
271
272 continue;
273 }
274
275 /*
276 * Try to uncompress the file.
277 */
278 if (!gunzip(inFile, outFile))
279 {
280 r = 1;
281 continue;
282 }
283
284 /*
285 * This was successful.
286 * Try to delete the original file now.
287 */
288 if (unlink(inFile) < 0)
289 {
290 fprintf(stderr, "%s: %s\n", inFile,
291 "Uncompressed ok but unlink failed");
292 r = 1;
293 }
294 }
295
296 return r;
297 }
298
299 /*
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.
303 */
304 if (isDevice(outPath))
305 {
306 while (!intFlag && (argc-- > 0))
307 {
308 if (!gunzip(*argv++, outPath))
309 r = 1;
310 }
311
312 return r;
313 }
314
315 /*
316 * If the output path is not a directory then either uncompress the
317 * single specified input file to the exactly specified output path,
318 * or else complain.
319 */
320 if (!isDirectory(outPath))
321 {
322 if (argc == 1)
323 return !gunzip(*argv, outPath);
324 else
325 fprintf(stderr, "Exactly one input file is required\n");
326
327 return 1;
328 }
329
330 /*
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.
334 */
335 while (!intFlag && (argc-- > 0))
336 {
337 inFile = *argv++;
338
339 /*
340 * Strip the path off of the input file name to make
341 * the beginnings of the output file name.
342 */
343 outFile = strrchr(inFile, '/');
344
345 if (outFile)
346 outFile++;
347 else
348 outFile = inFile;
349
350 /*
351 * Convert the extension of the output file name if possible.
352 * If we can't, then that is ok.
353 */
354 outFile = convertName(gunzipConvertTable, outFile);
355
356 /*
357 * Now build the output path name by prefixing it with
358 * the output directory.
359 */
360 outFile = buildName(outPath, outFile);
361
362 /*
363 * Uncompress the input file without deleting the input file.
364 */
365 if (!gunzip(inFile, outFile))
366 r = 1;
367 }
368
369 return r;
370 }
371
372
373 /*
374 * Compress the specified input file to produce the output file.
375 * Returns TRUE if successful.
376 */
377 static BOOL
gzip(const char * inputFileName,const char * outputFileName)378 gzip(const char * inputFileName, const char * outputFileName)
379 {
380 gzFile outGZ;
381 int inFD;
382 int len;
383 int err;
384 struct stat statBuf1;
385 struct stat statBuf2;
386 char buf[BUF_SIZE];
387
388 outGZ = NULL;
389 inFD = -1;
390
391 /*
392 * See if the output file is the same as the input file.
393 * If so, complain about it.
394 */
395 if (stat(inputFileName, &statBuf1) < 0)
396 {
397 perror(inputFileName);
398
399 return FALSE;
400 }
401
402 if (stat(outputFileName, &statBuf2) < 0)
403 {
404 statBuf2.st_ino = -1;
405 statBuf2.st_dev = -1;
406 }
407
408 if ((statBuf1.st_dev == statBuf2.st_dev) &&
409 (statBuf1.st_ino == statBuf2.st_ino))
410 {
411 fprintf(stderr,
412 "Cannot compress file \"%s\" on top of itself\n",
413 inputFileName);
414
415 return FALSE;
416 }
417
418 /*
419 * Open the input file.
420 */
421 inFD = open(inputFileName, O_RDONLY);
422
423 if (inFD < 0)
424 {
425 perror(inputFileName);
426
427 goto failed;
428 }
429
430 /*
431 * Ask the zlib library to open the output file.
432 */
433 outGZ = gzopen(outputFileName, "wb9");
434
435 if (outGZ == NULL)
436 {
437 fprintf(stderr, "%s: gzopen failed\n", outputFileName);
438
439 goto failed;
440 }
441
442 /*
443 * Read the uncompressed data from the input file and write
444 * the compressed data to the output file.
445 */
446 while ((len = read(inFD, buf, sizeof(buf))) > 0)
447 {
448 if (gzwrite(outGZ, buf, len) != len)
449 {
450 fprintf(stderr, "%s: %s\n", inputFileName,
451 gzerror(outGZ, &err));
452
453 goto failed;
454 }
455
456 if (intFlag)
457 goto failed;
458 }
459
460 if (len < 0)
461 {
462 perror(inputFileName);
463
464 goto failed;
465 }
466
467 /*
468 * All done, close the files.
469 */
470 if (close(inFD))
471 {
472 perror(inputFileName);
473
474 goto failed;
475 }
476
477 inFD = -1;
478
479 if (gzclose(outGZ) != Z_OK)
480 {
481 fprintf(stderr, "%s: gzclose failed\n", outputFileName);
482
483 goto failed;
484 }
485
486 outGZ = NULL;
487
488 /*
489 * Success.
490 */
491 return TRUE;
492
493
494 /*
495 * Here on an error, to clean up.
496 */
497 failed:
498 if (inFD >= 0)
499 (void) close(inFD);
500
501 if (outGZ != NULL)
502 (void) gzclose(outGZ);
503
504 return FALSE;
505 }
506
507
508 /*
509 * Uncompress the input file to produce the output file.
510 * Returns TRUE if successful.
511 */
512 static BOOL
gunzip(const char * inputFileName,const char * outputFileName)513 gunzip(const char * inputFileName, const char * outputFileName)
514 {
515 gzFile inGZ;
516 int outFD;
517 int len;
518 int err;
519 struct stat statBuf1;
520 struct stat statBuf2;
521 char buf[BUF_SIZE];
522
523 inGZ = NULL;
524 outFD = -1;
525
526 /*
527 * See if the output file is the same as the input file.
528 * If so, complain about it.
529 */
530 if (stat(inputFileName, &statBuf1) < 0)
531 {
532 perror(inputFileName);
533
534 return FALSE;
535 }
536
537 if (stat(outputFileName, &statBuf2) < 0)
538 {
539 statBuf2.st_ino = -1;
540 statBuf2.st_dev = -1;
541 }
542
543 if ((statBuf1.st_dev == statBuf2.st_dev) &&
544 (statBuf1.st_ino == statBuf2.st_ino))
545 {
546 fprintf(stderr,
547 "Cannot uncompress file \"%s\" on top of itself\n",
548 inputFileName);
549
550 return FALSE;
551 }
552
553 /*
554 * Ask the zlib library to open the input file.
555 */
556 inGZ = gzopen(inputFileName, "rb");
557
558 if (inGZ == NULL)
559 {
560 fprintf(stderr, "%s: gzopen failed\n", inputFileName);
561
562 return FALSE;
563 }
564
565 /*
566 * Create the output file.
567 */
568 if (isDevice(outputFileName))
569 outFD = open(outputFileName, O_WRONLY);
570 else
571 outFD = open(outputFileName, O_WRONLY|O_CREAT|O_TRUNC, 0666);
572
573 if (outFD < 0)
574 {
575 perror(outputFileName);
576
577 goto failed;
578 }
579
580 /*
581 * Read the compressed data from the input file and write
582 * the uncompressed data extracted from it to the output file.
583 */
584 while ((len = gzread(inGZ, buf, sizeof(buf))) > 0)
585 {
586 if (fullWrite(outFD, buf, len) < 0)
587 {
588 perror(outputFileName);
589
590 goto failed;
591 }
592
593 if (intFlag)
594 goto failed;
595 }
596
597 if (len < 0)
598 {
599 fprintf(stderr, "%s: %s\n", inputFileName,
600 gzerror(inGZ, &err));
601
602 goto failed;
603 }
604
605 /*
606 * All done, close the files.
607 */
608 if (close(outFD))
609 {
610 perror(outputFileName);
611
612 goto failed;
613 }
614
615 outFD = -1;
616
617 if (gzclose(inGZ) != Z_OK)
618 {
619 fprintf(stderr, "%s: gzclose failed\n", inputFileName);
620
621 goto failed;
622 }
623
624 inGZ = NULL;
625
626 /*
627 * Success.
628 */
629 return TRUE;
630
631
632 /*
633 * Here on an error, to clean up.
634 */
635 failed:
636 if (outFD >= 0)
637 (void) close(outFD);
638
639 if (inGZ != NULL)
640 (void) gzclose(inGZ);
641
642 return FALSE;
643 }
644
645
646 /*
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
652 * returned.
653 */
654 const char *
convertName(const CONVERT * table,const char * inputFile)655 convertName(const CONVERT * table, const char * inputFile)
656 {
657 int inputLength;
658 int testLength;
659 static char buf[PATH_LEN];
660
661 inputLength = strlen(inputFile);
662
663 for (;;)
664 {
665 testLength = strlen(table->input);
666
667 if ((inputLength >= testLength) &&
668 (memcmp(table->input,
669 inputFile + inputLength - testLength,
670 testLength) == 0))
671 {
672 break;
673 }
674
675 table++;
676 }
677
678 /*
679 * If no conversion was done, return the original pointer.
680 */
681 if ((testLength == 0) && (table->output[0] == '\0'))
682 return inputFile;
683
684 /*
685 * Build the new name and return it.
686 */
687 memcpy(buf, inputFile, inputLength - testLength);
688
689 memcpy(buf + inputLength - testLength, table->output,
690 strlen(table->output) + 1);
691
692 return buf;
693 }
694
695
696 #endif
697
698 /* END CODE */
699