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