Coverage Report - org.codehaus.plexus.archiver.tar.TarArchiver
 
Classes in this File Line Coverage Branch Coverage Complexity
TarArchiver
58 %
90/154
55 %
64/116
4,632
TarArchiver$TarCompressionMethod
85 %
6/7
N/A
4,632
TarArchiver$TarOptions
42 %
8/19
N/A
4,632
 
 1  0
 /**
 2  
  *
 3  
  * Copyright 2004 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * You may obtain a copy of the License at
 8  
  *
 9  
  * http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.codehaus.plexus.archiver.tar;
 18  
 
 19  
 import java.io.File;
 20  
 import java.io.FileOutputStream;
 21  
 import java.io.IOException;
 22  
 import java.io.InputStream;
 23  
 import java.io.OutputStream;
 24  
 import java.util.zip.GZIPOutputStream;
 25  
 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 26  
 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
 27  
 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
 28  
 import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
 29  
 import org.codehaus.plexus.archiver.AbstractArchiver;
 30  
 import org.codehaus.plexus.archiver.ArchiveEntry;
 31  
 import org.codehaus.plexus.archiver.ArchiverException;
 32  
 import org.codehaus.plexus.archiver.ResourceIterator;
 33  
 import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
 34  
 import org.codehaus.plexus.archiver.util.ResourceUtils;
 35  
 import org.codehaus.plexus.archiver.util.Streams;
 36  
 import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
 37  
 import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
 38  
 import org.codehaus.plexus.components.io.resources.PlexusIoResource;
 39  
 import org.codehaus.plexus.util.IOUtil;
 40  
 import org.codehaus.plexus.util.StringUtils;
 41  
 import org.iq80.snappy.SnappyOutputStream;
 42  
 import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
 43  
 
 44  
 /**
 45  
  * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
 46  
  */
 47  25
 public class TarArchiver
 48  
     extends AbstractArchiver
 49  
 {
 50  
 
 51  
     /**
 52  
      * Indicates whether the user has been warned about long files already.
 53  
      */
 54  25
     private boolean longWarningGiven = false;
 55  
 
 56  25
     private TarLongFileMode longFileMode = TarLongFileMode.warn;
 57  
 
 58  25
     private TarCompressionMethod compression = TarCompressionMethod.none;
 59  
 
 60  25
     private TarOptions options = new TarOptions();
 61  
 
 62  
     private TarArchiveOutputStream tOut;
 63  
 
 64  
     /**
 65  
      * Set how to handle long files, those with a path&gt;100 chars.
 66  
      * Optional, default=warn.
 67  
      * <p/>
 68  
      * Allowable values are
 69  
      * <ul>
 70  
      * <li> truncate - paths are truncated to the maximum length
 71  
      * <li> fail - paths greater than the maximum cause a build exception
 72  
      * <li> warn - paths greater than the maximum cause a warning and GNU is used
 73  
      * <li> gnu - GNU extensions are used for any paths greater than the maximum.
 74  
      * <li> posix - posix extensions are used for any paths greater than the maximum.
 75  
      * <li> posixwarn - posix extensions are used (with warning) for any paths greater than the maximum.
 76  
      * <li> omit - paths greater than the maximum are omitted from the archive
 77  
      * </ul>
 78  
      *
 79  
      * @param mode the mode to handle long file names.
 80  
      */
 81  
     public void setLongfile( TarLongFileMode mode )
 82  
     {
 83  24
         this.longFileMode = mode;
 84  24
     }
 85  
 
 86  
     /**
 87  
      * Set compression method.
 88  
      * Allowable values are
 89  
      * <ul>
 90  
      * <li> none - no compression
 91  
      * <li> gzip - Gzip compression
 92  
      * <li> bzip2 - Bzip2 compression
 93  
      * </ul>
 94  
      *
 95  
      * @param mode the compression method.
 96  
      */
 97  
     public void setCompression( TarCompressionMethod mode )
 98  
     {
 99  0
         this.compression = mode;
 100  0
     }
 101  
 
 102  
     @Override
 103  
     protected void execute()
 104  
         throws ArchiverException, IOException
 105  
     {
 106  24
         if ( !checkForced() )
 107  
         {
 108  0
             return;
 109  
         }
 110  
 
 111  24
         ResourceIterator iter = getResources();
 112  24
         if ( !iter.hasNext() )
 113  
         {
 114  1
             throw new EmptyArchiveException( "archive cannot be empty" );
 115  
         }
 116  
 
 117  23
         File tarFile = getDestFile();
 118  
 
 119  23
         if ( tarFile == null )
 120  
         {
 121  0
             throw new ArchiverException( "You must set the destination tar file." );
 122  
         }
 123  23
         if ( tarFile.exists() && !tarFile.isFile() )
 124  
         {
 125  0
             throw new ArchiverException( tarFile + " isn't a file." );
 126  
         }
 127  23
         if ( tarFile.exists() && !tarFile.canWrite() )
 128  
         {
 129  0
             throw new ArchiverException( tarFile + " is read-only." );
 130  
         }
 131  
 
 132  23
         getLogger().info( "Building tar: " + tarFile.getAbsolutePath() );
 133  
 
 134  
         try
 135  
         {
 136  23
             tOut = new TarArchiveOutputStream( compress( compression, new FileOutputStream( tarFile ) ), "UTF8" );
 137  23
             if ( longFileMode.isTruncateMode() )
 138  
             {
 139  0
                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_TRUNCATE );
 140  0
             }
 141  23
             else if ( longFileMode.isPosixMode() || longFileMode.isPosixWarnMode() )
 142  
             {
 143  22
                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
 144  
                 // Todo: Patch 2.5.1   for this fix. Also make closeable fix on 2.5.1
 145  22
                 tOut.setBigNumberMode( TarArchiveOutputStream.BIGNUMBER_POSIX );
 146  0
             }
 147  1
             else if ( longFileMode.isFailMode() || longFileMode.isOmitMode() )
 148  
             {
 149  0
                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_ERROR );
 150  0
             }
 151  
             else
 152  
             {
 153  
                 // warn or GNU
 154  1
                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_GNU );
 155  
             }
 156  
 
 157  23
             longWarningGiven = false;
 158  1452
             while ( iter.hasNext() )
 159  
             {
 160  1429
                 ArchiveEntry entry = iter.next();
 161  
                 // Check if we don't add tar file in itself
 162  1429
                 if ( ResourceUtils.isSame( entry.getResource(), tarFile ) )
 163  
                 {
 164  0
                     throw new ArchiverException( "A tar file cannot include itself." );
 165  
                 }
 166  1429
                 String fileName = entry.getName();
 167  1429
                 String name = StringUtils.replace( fileName, File.separatorChar, '/' );
 168  
 
 169  1429
                 tarFile( entry, tOut, name );
 170  1429
             }
 171  
 
 172  23
             tOut.close();
 173  0
         }
 174  
         finally
 175  0
         {
 176  23
             IOUtil.close( tOut );
 177  23
         }
 178  23
     }
 179  
 
 180  
     /**
 181  
      * tar a file
 182  
      *
 183  
      * @param entry the file to tar
 184  
      * @param tOut the output stream
 185  
      * @param vPath the path name of the file to tar
 186  
      *
 187  
      * @throws IOException on error
 188  
      */
 189  
     protected void tarFile( ArchiveEntry entry, TarArchiveOutputStream tOut, String vPath )
 190  
         throws ArchiverException, IOException
 191  
     {
 192  
 
 193  
         // don't add "" to the archive
 194  1429
         if ( vPath.length() <= 0 )
 195  
         {
 196  9
             return;
 197  
         }
 198  
 
 199  1420
         if ( entry.getResource().isDirectory() && !vPath.endsWith( "/" ) )
 200  
         {
 201  319
             vPath += "/";
 202  
         }
 203  
 
 204  1420
         if ( vPath.startsWith( "/" ) && !options.getPreserveLeadingSlashes() )
 205  
         {
 206  0
             int l = vPath.length();
 207  0
             if ( l <= 1 )
 208  
             {
 209  
                 // we would end up adding "" to the archive
 210  0
                 return;
 211  
             }
 212  0
             vPath = vPath.substring( 1, l );
 213  
         }
 214  
 
 215  1420
         int pathLength = vPath.length();
 216  1420
         InputStream fIn = null;
 217  
 
 218  
         try
 219  
         {
 220  
             TarArchiveEntry te;
 221  1420
             if ( !longFileMode.isGnuMode()
 222  0
                      && pathLength >= org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN )
 223  
             {
 224  8
                 int maxPosixPathLen = org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
 225  
                                           + org.apache.commons.compress.archivers.tar.TarConstants.PREFIXLEN;
 226  8
                 if ( longFileMode.isPosixMode() )
 227  
                 {
 228  
                 }
 229  0
                 else if ( longFileMode.isPosixWarnMode() )
 230  
                 {
 231  0
                     if ( pathLength > maxPosixPathLen )
 232  
                     {
 233  0
                         getLogger().warn( "Entry: " + vPath + " longer than " + maxPosixPathLen + " characters." );
 234  0
                         if ( !longWarningGiven )
 235  
                         {
 236  0
                             getLogger().warn( "Resulting tar file can only be processed "
 237  
                                                   + "successfully by GNU compatible tar commands" );
 238  0
                             longWarningGiven = true;
 239  
                         }
 240  
                     }
 241  0
                 }
 242  0
                 else if ( longFileMode.isOmitMode() )
 243  
                 {
 244  0
                     getLogger().info( "Omitting: " + vPath );
 245  0
                     return;
 246  
                 }
 247  0
                 else if ( longFileMode.isWarnMode() )
 248  
                 {
 249  0
                     getLogger().warn( "Entry: " + vPath + " longer than "
 250  0
                                           + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
 251  0
                                           + " characters." );
 252  0
                     if ( !longWarningGiven )
 253  
                     {
 254  0
                         getLogger().warn( "Resulting tar file can only be processed "
 255  
                                               + "successfully by GNU compatible tar commands" );
 256  0
                         longWarningGiven = true;
 257  
                     }
 258  0
                 }
 259  0
                 else if ( longFileMode.isFailMode() )
 260  
                 {
 261  0
                     throw new ArchiverException( "Entry: " + vPath + " longer than "
 262  0
                                                      + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
 263  0
                                                      + " characters." );
 264  
                 }
 265  
                 else
 266  
                 {
 267  0
                     throw new IllegalStateException( "Non gnu mode should never get here?" );
 268  
                 }
 269  
             }
 270  
 
 271  1420
             if ( entry.getType() == ArchiveEntry.SYMLINK )
 272  
             {
 273  37
                 final SymlinkDestinationSupplier plexusIoSymlinkResource =
 274  0
                     (SymlinkDestinationSupplier) entry.getResource();
 275  
 
 276  37
                 te = new TarArchiveEntry( vPath, TarArchiveEntry.LF_SYMLINK );
 277  37
                 te.setLinkName( plexusIoSymlinkResource.getSymlinkDestination() );
 278  37
             }
 279  
             else
 280  
             {
 281  1383
                 te = new TarArchiveEntry( vPath );
 282  
             }
 283  
 
 284  1420
             long teLastModified = entry.getResource().getLastModified();
 285  1420
             te.setModTime( teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
 286  0
                                ? System.currentTimeMillis()
 287  0
                                : teLastModified );
 288  
 
 289  1420
             if ( entry.getType() == ArchiveEntry.SYMLINK )
 290  
             {
 291  37
                 te.setSize( 0 );
 292  
 
 293  0
             }
 294  1383
             else if ( !entry.getResource().isDirectory() )
 295  
             {
 296  1067
                 final long size = entry.getResource().getSize();
 297  1067
                 te.setSize( size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size );
 298  
             }
 299  1420
             te.setMode( entry.getMode() );
 300  
 
 301  1420
             PlexusIoResourceAttributes attributes = entry.getResourceAttributes();
 302  
 
 303  1420
             te.setUserName( ( attributes != null && attributes.getUserName() != null )
 304  0
                                 ? attributes.getUserName()
 305  0
                                 : options.getUserName() );
 306  1420
             te.setGroupName( ( attributes != null && attributes.getGroupName() != null )
 307  0
                                  ? attributes.getGroupName()
 308  0
                                  : options.getGroup() );
 309  
 
 310  1420
             final int userId =
 311  0
                 ( attributes != null && attributes.getUserId() != null ) ? attributes.getUserId() : options.getUid();
 312  1420
             if ( userId >= 0 )
 313  
             {
 314  1420
                 te.setUserId( userId );
 315  
             }
 316  
 
 317  1420
             final int groupId =
 318  0
                 ( attributes != null && attributes.getGroupId() != null ) ? attributes.getGroupId() : options.getGid();
 319  1420
             if ( groupId >= 0 )
 320  
             {
 321  1420
                 te.setGroupId( groupId );
 322  
             }
 323  
 
 324  1420
             tOut.putArchiveEntry( te );
 325  
 
 326  
             try
 327  
             {
 328  1420
                 if ( entry.getResource().isFile() && !( entry.getType() == ArchiveEntry.SYMLINK ) )
 329  
                 {
 330  1067
                     fIn = entry.getInputStream();
 331  
 
 332  1067
                     Streams.copyFullyDontCloseOutput( fIn, tOut, "xAR" );
 333  
                 }
 334  
 
 335  0
             }
 336  0
             catch ( Throwable e )
 337  
             {
 338  0
                 getLogger().warn( "When creating tar entry", e );
 339  
             }
 340  
             finally
 341  0
             {
 342  1420
                 tOut.closeArchiveEntry();
 343  1420
             }
 344  0
         }
 345  
         finally
 346  0
         {
 347  1420
             IOUtil.close( fIn );
 348  1420
         }
 349  1420
     }
 350  
 
 351  
     /**
 352  
      * Valid Modes for Compression attribute to Tar Task
 353  
      */
 354  25
     public class TarOptions
 355  
     {
 356  
 
 357  25
         private String userName = "";
 358  
 
 359  25
         private String groupName = "";
 360  
 
 361  
         private int uid;
 362  
 
 363  
         private int gid;
 364  
 
 365  25
         private boolean preserveLeadingSlashes = false;
 366  
 
 367  
         /**
 368  
          * The username for the tar entry
 369  
          * This is not the same as the UID.
 370  
          *
 371  
          * @param userName the user name for the tar entry.
 372  
          */
 373  
         public void setUserName( String userName )
 374  
         {
 375  0
             this.userName = userName;
 376  0
         }
 377  
 
 378  
         /**
 379  
          * @return the user name for the tar entry
 380  
          */
 381  
         public String getUserName()
 382  
         {
 383  3
             return userName;
 384  
         }
 385  
 
 386  
         /**
 387  
          * The uid for the tar entry
 388  
          * This is not the same as the User name.
 389  
          *
 390  
          * @param uid the id of the user for the tar entry.
 391  
          */
 392  
         public void setUid( int uid )
 393  
         {
 394  0
             this.uid = uid;
 395  0
         }
 396  
 
 397  
         /**
 398  
          * @return the uid for the tar entry
 399  
          */
 400  
         public int getUid()
 401  
         {
 402  3
             return uid;
 403  
         }
 404  
 
 405  
         /**
 406  
          * The groupname for the tar entry; optional, default=""
 407  
          * This is not the same as the GID.
 408  
          *
 409  
          * @param groupName the group name string.
 410  
          */
 411  
         public void setGroup( String groupName )
 412  
         {
 413  0
             this.groupName = groupName;
 414  0
         }
 415  
 
 416  
         /**
 417  
          * @return the group name string.
 418  
          */
 419  
         public String getGroup()
 420  
         {
 421  3
             return groupName;
 422  
         }
 423  
 
 424  
         /**
 425  
          * The GID for the tar entry; optional, default="0"
 426  
          * This is not the same as the group name.
 427  
          *
 428  
          * @param gid the group id.
 429  
          */
 430  
         public void setGid( int gid )
 431  
         {
 432  0
             this.gid = gid;
 433  0
         }
 434  
 
 435  
         /**
 436  
          * @return the group identifier.
 437  
          */
 438  
         public int getGid()
 439  
         {
 440  3
             return gid;
 441  
         }
 442  
 
 443  
         /**
 444  
          * @return the leading slashes flag.
 445  
          */
 446  
         public boolean getPreserveLeadingSlashes()
 447  
         {
 448  0
             return preserveLeadingSlashes;
 449  
         }
 450  
 
 451  
         /**
 452  
          * Flag to indicates whether leading `/'s should
 453  
          * be preserved in the file names.
 454  
          * Optional, default is <code>false</code>.
 455  
          *
 456  
          * @param preserveLeadingSlashes the leading slashes flag.
 457  
          */
 458  
         public void setPreserveLeadingSlashes( boolean preserveLeadingSlashes )
 459  
         {
 460  0
             this.preserveLeadingSlashes = preserveLeadingSlashes;
 461  0
         }
 462  
 
 463  
     }
 464  
 
 465  
     /**
 466  
      * Valid Modes for Compression attribute to Tar Task
 467  
      */
 468  25
     public static enum TarCompressionMethod
 469  
     {
 470  
 
 471  1
         none,
 472  1
         gzip,
 473  1
         bzip2,
 474  1
         snappy,
 475  1
         xz
 476  
 
 477  
     }
 478  
 
 479  
     private OutputStream compress( TarCompressionMethod tarCompressionMethod, final OutputStream ostream )
 480  
         throws IOException
 481  
     {
 482  23
         if ( TarCompressionMethod.gzip.equals( tarCompressionMethod ) )
 483  
         {
 484  0
             return Streams.bufferedOutputStream( new GZIPOutputStream( ostream ) );
 485  
         }
 486  23
         else if ( TarCompressionMethod.bzip2.equals( tarCompressionMethod ) )
 487  
         {
 488  0
             return new BZip2CompressorOutputStream( bufferedOutputStream( ostream ) );
 489  
         }
 490  23
         else if ( TarCompressionMethod.snappy.equals( tarCompressionMethod ) )
 491  
         {
 492  0
             return new SnappyOutputStream( bufferedOutputStream( ostream ) );
 493  
         }
 494  23
         else if ( TarCompressionMethod.xz.equals( tarCompressionMethod ) )
 495  
         {
 496  0
             return new XZCompressorOutputStream( bufferedOutputStream( ostream ) );
 497  
         }
 498  
 
 499  23
         return ostream;
 500  
     }
 501  
 
 502  
     @Override
 503  
     public boolean isSupportingForced()
 504  
     {
 505  0
         return true;
 506  
     }
 507  
 
 508  
     @Override
 509  
     protected void cleanUp()
 510  
         throws IOException
 511  
     {
 512  24
         super.cleanUp();
 513  24
         if ( this.tOut != null )
 514  
         {
 515  23
             this.tOut.close();
 516  
         }
 517  24
     }
 518  
 
 519  
     @Override
 520  
     protected void close()
 521  
         throws IOException
 522  
     {
 523  24
         if ( this.tOut != null )
 524  
         {
 525  23
             this.tOut.close();
 526  
         }
 527  24
     }
 528  
 
 529  
     @Override
 530  
     protected String getArchiveType()
 531  
     {
 532  0
         return "TAR";
 533  
     }
 534  
 
 535  
 }