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