001/*
002** Authored by Timothy Gerard Endres
003** <mailto:time@gjt.org>  <http://www.trustice.com>
004** 
005** This work has been placed into the public domain.
006** You may use this work in any way and for any purpose you wish.
007**
008** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
009** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
010** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
011** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
012** REDISTRIBUTION OF THIS SOFTWARE. 
013** 
014*/
015
016package com.ice.tar;
017
018import java.io.*;
019import java.net.*;
020import java.util.zip.*;
021import javax.activation.*;
022
023/**
024 * The tar class implements a weak reproduction of the
025 * traditional UNIX tar command. It currently supports
026 * creating, listing, and extracting from archives. It
027 * also supports GZIP-ed archives with the '-z' flag.
028 * See the usage (-? or --usage) for option details.
029 *
030 * <pre>
031 * usage: com.ice.tar.tar has three basic modes:
032 * 
033 *   com.ice.tar -c [options] archive files...
034 *      Create new archive containing files.
035 * 
036 *   com.ice.tar -t [options] archive
037 *      List contents of tar archive
038 * 
039 *   com.ice.tar -x [options] archive
040 *      Extract contents of tar archive.
041 * 
042 * options:
043 *    -f file, use 'file' as the tar archive
044 *    -v, verbose mode
045 *    -z, use GZIP compression
046 *    -D, debug archive and buffer operation
047 *    -b blks, set blocking size to (blks * 512) bytes
048 *    -o, write a V7 format archive rather than ANSI
049 *    -u name, set user name to 'name'
050 *    -U id, set user id to 'id'
051 *    -g name, set group name to 'name'
052 *    -G id, set group id to 'id'
053 *    -?, print usage information
054 *    --trans, translate 'text/*' files
055 *    --mime file, use this mime types file and translate
056 *    --usage, print usage information
057 *    --version, print version information
058 * 
059 * The translation options will translate from local line
060 * endings to UNIX line endings of '\\n' when writing tar
061 * archives, and from UNIX line endings into local line endings
062 * when extracting archives.
063 * 
064 * Written by Tim Endres
065 * This software has been placed into the public domain.
066 * </pre>
067 *
068 * @version $Revision: 1.10 $
069 * @author Timothy Gerard Endres, <time@gjt.org>
070 * @see TarArchive
071 */
072
073public class
074tar extends Object
075        implements TarProgressDisplay
076        {
077        /**
078         * Flag that determines if debugging information is displayed.
079         */
080        private boolean         debug;
081        /**
082         * Flag that determines if verbose feedback is provided.
083         */
084        private boolean         verbose;
085        /**
086         * Flag that determines if IO is GZIP-ed ('-z' option).
087         */
088        private boolean         compressed;
089        /**
090         * True if we are listing the archive. False if writing or extracting.
091         */
092        private boolean         listingArchive;
093        /**
094         * True if we are writing the archive. False if we are extracting it.
095         */
096        private boolean         writingArchive;
097        /**
098         * True if we are writing an old UNIX archive format (sets entry format).
099         */
100        private boolean         unixArchiveFormat;
101        /**
102         * True if we are not to overwrite existing files.
103         */
104        private boolean         keepOldFiles;
105        /**
106         * True if we are to convert ASCII text files from local line endings
107         * to the UNIX standard '\n'.
108         */
109        private boolean         asciiTranslate;
110        /**
111         * True if a MIME file has been loaded with the '--mime' option.
112         */
113        private boolean         mimeFileLoaded;
114
115        /**
116         * The archive name provided on the command line, null if stdio.
117         */
118        private String          archiveName;
119
120        /**
121         * The blocksize to use for the tar archive IO. Set by the '-b' option.
122         */
123        private int                     blockSize;
124
125        /**
126         * The userId to use for files written to archives. Set by '-U' option.
127         */
128        private int                     userId;
129        /**
130         * The userName to use for files written to archives. Set by '-u' option.
131         */
132        private String          userName;
133        /**
134         * The groupId to use for files written to archives. Set by '-G' option.
135         */
136        private int                     groupId;
137        /**
138         * The groupName to use for files written to archives. Set by '-g' option.
139         */
140        private String          groupName;
141
142
143        /**
144         * The main entry point of the tar class.
145         */
146        public static void
147        main( String argv[] )
148                {
149                tar app = new tar();
150
151                app.instanceMain( argv );
152                }
153
154        /**
155         * Establishes the default userName with the 'user.name' property.
156         */
157        public
158        tar()
159                {
160                this.debug = false;
161                this.verbose = false;
162                this.compressed = false;
163                this.archiveName = null;
164                this.listingArchive = false;
165                this.writingArchive = true;
166                this.unixArchiveFormat = false;
167                this.keepOldFiles = false;
168                this.asciiTranslate = false;
169
170                this.blockSize = TarBuffer.DEFAULT_BLKSIZE;
171
172                String sysUserName =
173                        System.getProperty( "user.name" );
174
175                this.userId = 0;
176                this.userName =
177                        ( (sysUserName == null) ? "" : sysUserName );
178
179                this.groupId = 0;
180                this.groupName = "";
181                }
182
183        /**
184         * This is the "real" main. The class main() instantiates a tar object
185         * for the application and then calls this method. Process the arguments
186         * and perform the requested operation.
187         */
188        public void
189        instanceMain( String argv[] )
190                {
191                TarArchive archive = null;
192
193                int argIdx = this.processArguments( argv );
194
195                if ( writingArchive )                           // WRITING
196                        {
197                        OutputStream outStream = System.out;
198
199                        if ( this.archiveName != null
200                                        && ! this.archiveName.equals( "-" ) )
201                                {
202                                try {
203                                        outStream = new FileOutputStream( this.archiveName );
204                                        }
205                                catch ( IOException ex )
206                                        {
207                                        outStream = null;
208                                        ex.printStackTrace( System.err );
209                                        }
210                                }
211
212                        if ( outStream != null )
213                                {
214                                if ( this.compressed )
215                                        {
216                                        try {
217                                                outStream = new GZIPOutputStream( outStream );
218                                                }
219                                        catch ( IOException ex )
220                                                {
221                                                outStream = null;
222                                                ex.printStackTrace( System.err );
223                                                }
224                                        }
225                                
226                                archive = new TarArchive( outStream, this.blockSize );
227                                }
228                        }
229                else                                                            // EXTRACING OR LISTING
230                        {
231                        InputStream inStream = System.in;
232
233                        if ( this.archiveName != null
234                                        && ! this.archiveName.equals( "-" ) )
235                                {
236                                try {
237                                        inStream = new FileInputStream( this.archiveName );
238                                        }
239                                catch ( IOException ex )
240                                        {
241                                        inStream = null;
242                                        ex.printStackTrace( System.err );
243                                        }
244                                }
245
246                        if ( inStream != null )
247                                {
248                                if ( this.compressed )
249                                        {
250                                        try {
251                                                inStream = new GZIPInputStream( inStream );
252                                                }
253                                        catch ( IOException ex )
254                                                {
255                                                inStream = null;
256                                                ex.printStackTrace( System.err );
257                                                }
258                                        }
259
260                                archive = new TarArchive( inStream, this.blockSize );
261                                }
262                        }
263
264                if ( archive != null )                                          // SET ARCHIVE OPTIONS
265                        {
266                        archive.setDebug( this.debug );
267                        archive.setVerbose( this.verbose );
268                        archive.setTarProgressDisplay( this );
269                        archive.setKeepOldFiles( this.keepOldFiles );
270                        archive.setAsciiTranslation( this.asciiTranslate );
271
272                        archive.setUserInfo(
273                                        this.userId, this.userName,
274                                        this.groupId, this.groupName );
275                        }
276
277                if ( archive == null )
278                        {
279                        System.err.println( "no processing due to errors" );
280                        }
281                else if ( this.writingArchive )                         // WRITING
282                        {
283                        for ( ; argIdx < argv.length ; ++argIdx )
284                                {
285                                try {
286                                        File f = new File( argv[ argIdx ] );
287                                
288                                        TarEntry entry = new TarEntry( f );
289
290                                        if ( this.unixArchiveFormat )
291                                                entry.setUnixTarFormat();
292                                        else
293                                                entry.setUSTarFormat();
294
295                                        archive.writeEntry( entry, true );
296                                        }
297                                catch ( IOException ex )
298                                        {
299                                        ex.printStackTrace( System.err );
300                                        }
301                                }
302                        }
303                else if ( this.listingArchive )                         // LISTING
304                        {
305                        try {
306                                archive.listContents();
307                                }
308                        catch ( InvalidHeaderException ex )
309                                {
310                                ex.printStackTrace( System.err );
311                                }
312                        catch ( IOException ex )
313                                {
314                                ex.printStackTrace( System.err );
315                                }
316                        }
317                else                                                                            // EXTRACTING
318                        {
319                        String userDir =
320                                System.getProperty( "user.dir", null );
321
322                        File destDir = new File( userDir );
323                        if ( ! destDir.exists() )
324                                {
325                                if ( ! destDir.mkdirs() )
326                                        {
327                                        destDir = null;
328                                        Throwable ex = new Throwable
329                                                ( "ERROR, mkdirs() on '" + destDir.getPath()
330                                                        + "' returned false." );
331                                        ex.printStackTrace( System.err );
332                                        }
333                                }
334
335                        if ( destDir != null )
336                                {
337                                try {
338                                        archive.extractContents( destDir );
339                                        }
340                                catch ( InvalidHeaderException ex )
341                                        {
342                                        ex.printStackTrace( System.err );
343                                        }
344                                catch ( IOException ex )
345                                        {
346                                        ex.printStackTrace( System.err );
347                                        }
348                                }
349                        }
350
351                if ( archive != null )                                          // CLOSE ARCHIVE
352                        {
353                        try {
354                                archive.closeArchive();
355                                }
356                        catch ( IOException ex )
357                                {
358                                ex.printStackTrace( System.err );
359                                }
360                        }
361                }
362
363        /**
364         * Process arguments, handling options, and return the index of the
365         * first non-option argument.
366         *
367         * @return The index of the first non-option argument.
368         */
369
370        private int
371        processArguments( String args[] )
372                {
373                int idx = 0;
374                boolean gotOP = false;
375
376                for ( ; idx < args.length ; ++idx )
377                        {
378                        String arg = args[ idx ];
379
380                        if ( ! arg.startsWith( "-" ) )
381                                break;
382
383                        if ( arg.startsWith( "--" ) )
384                                {
385                                if ( arg.equals( "--usage" ) )
386                                        {
387                                        this.usage();
388                                        System.exit(1);
389                                        }
390                                else if ( arg.equals( "--version" ) )
391                                        {
392                                        this.version();
393                                        System.exit(1);
394                                        }
395                                else if ( arg.equals( "--trans" ) )
396                                        {
397                                        this.asciiTranslate = true;
398
399                                        String jafClassName =
400                                                "javax.activation.FileTypeMap";
401
402                                        try {
403                                                Class jafClass =
404                                                        Class.forName( jafClassName );
405
406                                                if ( ! this.mimeFileLoaded )
407                                                        {
408                                                        URL mimeURL =
409                                                                tar.class.getResource
410                                                                        ( "/com/ice/tar/asciimime.txt" );
411
412                                                        URLConnection mimeConn =
413                                                                mimeURL.openConnection();
414
415                                                        InputStream in = mimeConn.getInputStream();
416
417                                                        FileTypeMap.setDefaultFileTypeMap
418                                                                ( new MimetypesFileTypeMap( in ) );
419                                                        }
420                                                }
421                                        catch ( ClassNotFoundException ex )
422                                                {
423                                                System.err.println
424                                                ( "Could not load the class named '"
425                                                        + jafClassName + "'.\n"
426                                                        + "The Java Activation package must "
427                                                        + "be installed to use ascii translation." );
428                                                System.exit(1);
429                                                }
430                                        catch ( IOException ex )
431                                                {
432                                                ex.printStackTrace( System.err );
433                                                System.exit(1);
434                                                }
435                                        }
436                                else if ( arg.equals( "--mime" ) )
437                                        {
438                                        this.mimeFileLoaded = true;
439
440                                        String jafClassName =
441                                                "javax.activation.FileTypeMap";
442
443                                        File mimeFile = new File( args[ ++idx ] );
444
445                                        try {
446                                                Class jafClass =
447                                                        Class.forName( jafClassName );
448
449                                                FileTypeMap.setDefaultFileTypeMap(
450                                                        new MimetypesFileTypeMap(
451                                                                new FileInputStream( mimeFile ) ) );
452                                                }
453                                        catch ( ClassNotFoundException ex )
454                                                {
455                                                System.err.println
456                                                ( "Could not load the class named '"
457                                                        + jafClassName + "'.\n"
458                                                        + "The Java Activation package must "
459                                                        + "be installed to use ascii translation." );
460                                                System.exit(1);
461                                                }
462                                        catch ( FileNotFoundException ex )
463                                                {
464                                                System.err.println
465                                                ( "Could not open the mimetypes file '"
466                                                        + mimeFile.getPath() + "', " + ex.getMessage() );
467                                                }
468                                        }
469                                else
470                                        {
471                                        System.err.println
472                                                ( "unknown option: " + arg );
473                                        this.usage();
474                                        System.exit(1);
475                                        }
476                                }
477                        else for ( int cIdx = 1 ; cIdx < arg.length() ; ++cIdx )
478                                {
479                                char ch = arg.charAt( cIdx );
480
481                                if ( ch == '?' )
482                                        {
483                                        this.usage();
484                                        System.exit(1);
485                                        }
486                                else if ( ch == 'f' )
487                                        {
488                                        this.archiveName = args[ ++idx ];
489                                        }
490                                else if ( ch == 'z' )
491                                        {
492                                        this.compressed = true;
493                                        }
494                                else if ( ch == 'c' )
495                                        {
496                                        gotOP = true;
497                                        this.writingArchive = true;
498                                        this.listingArchive = false;
499                                        }
500                                else if ( ch == 'x' )
501                                        {
502                                        gotOP = true;
503                                        this.writingArchive = false;
504                                        this.listingArchive = false;
505                                        }
506                                else if ( ch == 't' )
507                                        {
508                                        gotOP = true;
509                                        this.writingArchive = false;
510                                        this.listingArchive = true;
511                                        }
512                                else if ( ch == 'k' )
513                                        {
514                                        this.keepOldFiles = true;
515                                        }
516                                else if ( ch == 'o' )
517                                        {
518                                        this.unixArchiveFormat = true;
519                                        }
520                                else if ( ch == 'b' )
521                                        {
522                                        try {
523                                                int blks = Integer.parseInt( args[ ++idx ] );
524                                                this.blockSize =
525                                                        ( blks * TarBuffer.DEFAULT_RCDSIZE );
526                                                }
527                                        catch ( NumberFormatException ex )
528                                                {
529                                                ex.printStackTrace( System.err );
530                                                }
531                                        }
532                                else if ( ch == 'u' )
533                                        {
534                                        this.userName = args[ ++idx ];
535                                        }
536                                else if ( ch == 'U' )
537                                        {
538                                        String idStr = args[ ++idx ];
539                                        try {
540                                                this.userId = Integer.parseInt( idStr );
541                                                }
542                                        catch ( NumberFormatException ex )
543                                                {
544                                                this.userId = 0;
545                                                ex.printStackTrace( System.err );
546                                                }
547                                        }
548                                else if ( ch == 'g' )
549                                        {
550                                        this.groupName = args[ ++idx ];
551                                        }
552                                else if ( ch == 'G' )
553                                        {
554                                        String idStr = args[ ++idx ];
555                                        try {
556                                                this.groupId = Integer.parseInt( idStr );
557                                                }
558                                        catch ( NumberFormatException ex )
559                                                {
560                                                this.groupId = 0;
561                                                ex.printStackTrace( System.err );
562                                                }
563                                        }
564                                else if ( ch == 'v' )
565                                        {
566                                        this.verbose = true;
567                                        }
568                                else if ( ch == 'D' )
569                                        {
570                                        this.debug = true;
571                                        }
572                                else
573                                        {
574                                        System.err.println
575                                                ( "unknown option: " + ch );
576                                        this.usage();
577                                        System.exit(1);
578                                        }
579                                }
580                        }
581
582                if ( ! gotOP )
583                        {
584                        System.err.println
585                                ( "you must specify an operation option (c, x, or t)" );
586                        this.usage();
587                        System.exit(1);
588                        }
589
590                return idx;
591                }
592
593         // I N T E R F A C E   TarProgressDisplay
594
595         /**
596         * Display progress information by printing it to System.out.
597         */
598
599        public void
600        showTarProgressMessage( String msg )
601                {
602                System.out.println( msg );
603                }
604
605        /**
606         * Print version information.
607         */
608
609        private void
610        version()
611                {
612                System.err.println
613                        ( "Release 2.4 - $Revision: 1.10 $ $Name:  $" );
614                }
615
616        /**
617         * Print usage information.
618         */
619
620        private void
621        usage()
622                {
623                System.err.println( "usage: com.ice.tar.tar has three basic modes:" );
624                System.err.println( "  com.ice.tar -c [options] archive files..." );
625                System.err.println( "    Create new archive containing files." );
626                System.err.println( "  com.ice.tar -t [options] archive" );
627                System.err.println( "    List contents of tar archive" );
628                System.err.println( "  com.ice.tar -x [options] archive" );
629                System.err.println( "    Extract contents of tar archive." );
630                System.err.println( "" );
631                System.err.println( "options:" );
632                System.err.println( "   -f file, use 'file' as the tar archive" );
633                System.err.println( "   -v, verbose mode" );
634                System.err.println( "   -z, use GZIP compression" );
635                System.err.println( "   -D, debug archive and buffer operation" );
636                System.err.println( "   -b blks, set blocking size to (blks * 512) bytes" );
637                System.err.println( "   -o, write a V7 format archive rather than ANSI" );
638                System.err.println( "   -u name, set user name to 'name'" );
639                System.err.println( "   -U id, set user id to 'id'" );
640                System.err.println( "   -g name, set group name to 'name'" );
641                System.err.println( "   -G id, set group id to 'id'" );
642                System.err.println( "   -?, print usage information" );
643                System.err.println( "   --trans, translate 'text/*' files" );
644                System.err.println( "   --mime file, use this mime types file and translate" );
645                System.err.println( "   --usage, print usage information" );
646                System.err.println( "   --version, print version information" );
647                System.err.println( "" );
648                System.err.println( "The translation options will translate from local line" );
649                System.err.println( "endings to UNIX line endings of '\\n' when writing tar" );
650                System.err.println( "archives, and from UNIX line endings into local line endings" );
651                System.err.println( "when extracting archives." );
652                System.err.println( "" );
653                System.err.println( "Written by Tim Endres" );
654                System.err.println( "" );
655                System.err.println( "This software has been placed into the public domain." );
656                System.err.println( "" );
657
658                this.version();
659
660                System.exit( 1 );
661                }
662
663        }
664
665