]> err.no Git - sash/blob - cmd_gzip.c
Stop stripping during build. Also thanks to Helmut Grohne. Closes: #852771
[sash] / cmd_gzip.c
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
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
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
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
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 *
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 */