org.codehaus.plexus.archiver.tar.TarArchiver Maven / Gradle / Ivy
/**
*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.plexus.archiver.tar;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.codehaus.plexus.archiver.AbstractArchiver;
import org.codehaus.plexus.archiver.ArchiveEntry;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.ResourceIterator;
import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
import org.codehaus.plexus.archiver.util.ResourceUtils;
import org.codehaus.plexus.archiver.util.Streams;
import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.iq80.snappy.SnappyOutputStream;
import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
/**
* @author Emmanuel Venisse
*/
public class TarArchiver
extends AbstractArchiver
{
/**
* Indicates whether the user has been warned about long files already.
*/
private boolean longWarningGiven = false;
private TarLongFileMode longFileMode = TarLongFileMode.warn;
private TarCompressionMethod compression = TarCompressionMethod.none;
private TarOptions options = new TarOptions();
private TarArchiveOutputStream tOut;
/**
* Set how to handle long files, those with a path>100 chars.
* Optional, default=warn.
*
* Allowable values are
*
* - truncate - paths are truncated to the maximum length
* - fail - paths greater than the maximum cause a build exception
* - warn - paths greater than the maximum cause a warning and GNU is used
* - gnu - GNU extensions are used for any paths greater than the maximum.
* - posix - posix extensions are used for any paths greater than the maximum.
* - posixwarn - posix extensions are used (with warning) for any paths greater than the maximum.
* - omit - paths greater than the maximum are omitted from the archive
*
*
* @param mode the mode to handle long file names.
*/
public void setLongfile( TarLongFileMode mode )
{
this.longFileMode = mode;
}
/**
* Set compression method.
* Allowable values are
*
* - none - no compression
*
- gzip - Gzip compression
*
- bzip2 - Bzip2 compression
*
*
* @param mode the compression method.
*/
public void setCompression( TarCompressionMethod mode )
{
this.compression = mode;
}
@Override
protected void execute()
throws ArchiverException, IOException
{
if ( !checkForced() )
{
return;
}
ResourceIterator iter = getResources();
if ( !iter.hasNext() )
{
throw new EmptyArchiveException( "archive cannot be empty" );
}
File tarFile = getDestFile();
if ( tarFile == null )
{
throw new ArchiverException( "You must set the destination tar file." );
}
if ( tarFile.exists() && !tarFile.isFile() )
{
throw new ArchiverException( tarFile + " isn't a file." );
}
if ( tarFile.exists() && !tarFile.canWrite() )
{
throw new ArchiverException( tarFile + " is read-only." );
}
getLogger().info( "Building tar: " + tarFile.getAbsolutePath() );
try
{
tOut = new TarArchiveOutputStream( compress( compression, new FileOutputStream( tarFile ) ), "UTF8" );
if ( longFileMode.isTruncateMode() )
{
tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_TRUNCATE );
}
else if ( longFileMode.isPosixMode() || longFileMode.isPosixWarnMode() )
{
tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
// Todo: Patch 2.5.1 for this fix. Also make closeable fix on 2.5.1
tOut.setBigNumberMode( TarArchiveOutputStream.BIGNUMBER_POSIX );
}
else if ( longFileMode.isFailMode() || longFileMode.isOmitMode() )
{
tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_ERROR );
}
else
{
// warn or GNU
tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_GNU );
}
longWarningGiven = false;
while ( iter.hasNext() )
{
ArchiveEntry entry = iter.next();
// Check if we don't add tar file in itself
if ( ResourceUtils.isSame( entry.getResource(), tarFile ) )
{
throw new ArchiverException( "A tar file cannot include itself." );
}
String fileName = entry.getName();
String name = StringUtils.replace( fileName, File.separatorChar, '/' );
tarFile( entry, tOut, name );
}
tOut.close();
}
finally
{
IOUtil.close( tOut );
}
}
/**
* tar a file
*
* @param entry the file to tar
* @param tOut the output stream
* @param vPath the path name of the file to tar
*
* @throws IOException on error
*/
protected void tarFile( ArchiveEntry entry, TarArchiveOutputStream tOut, String vPath )
throws ArchiverException, IOException
{
// don't add "" to the archive
if ( vPath.length() <= 0 )
{
return;
}
if ( entry.getResource().isDirectory() && !vPath.endsWith( "/" ) )
{
vPath += "/";
}
if ( vPath.startsWith( "/" ) && !options.getPreserveLeadingSlashes() )
{
int l = vPath.length();
if ( l <= 1 )
{
// we would end up adding "" to the archive
return;
}
vPath = vPath.substring( 1, l );
}
int pathLength = vPath.length();
InputStream fIn = null;
try
{
TarArchiveEntry te;
if ( !longFileMode.isGnuMode()
&& pathLength >= org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN )
{
int maxPosixPathLen = org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
+ org.apache.commons.compress.archivers.tar.TarConstants.PREFIXLEN;
if ( longFileMode.isPosixMode() )
{
}
else if ( longFileMode.isPosixWarnMode() )
{
if ( pathLength > maxPosixPathLen )
{
getLogger().warn( "Entry: " + vPath + " longer than " + maxPosixPathLen + " characters." );
if ( !longWarningGiven )
{
getLogger().warn( "Resulting tar file can only be processed "
+ "successfully by GNU compatible tar commands" );
longWarningGiven = true;
}
}
}
else if ( longFileMode.isOmitMode() )
{
getLogger().info( "Omitting: " + vPath );
return;
}
else if ( longFileMode.isWarnMode() )
{
getLogger().warn( "Entry: " + vPath + " longer than "
+ org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
+ " characters." );
if ( !longWarningGiven )
{
getLogger().warn( "Resulting tar file can only be processed "
+ "successfully by GNU compatible tar commands" );
longWarningGiven = true;
}
}
else if ( longFileMode.isFailMode() )
{
throw new ArchiverException( "Entry: " + vPath + " longer than "
+ org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
+ " characters." );
}
else
{
throw new IllegalStateException( "Non gnu mode should never get here?" );
}
}
if ( entry.getType() == ArchiveEntry.SYMLINK )
{
final SymlinkDestinationSupplier plexusIoSymlinkResource =
(SymlinkDestinationSupplier) entry.getResource();
te = new TarArchiveEntry( vPath, TarArchiveEntry.LF_SYMLINK );
te.setLinkName( plexusIoSymlinkResource.getSymlinkDestination() );
}
else
{
te = new TarArchiveEntry( vPath );
}
long teLastModified = entry.getResource().getLastModified();
te.setModTime( teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
? System.currentTimeMillis()
: teLastModified );
if ( entry.getType() == ArchiveEntry.SYMLINK )
{
te.setSize( 0 );
}
else if ( !entry.getResource().isDirectory() )
{
final long size = entry.getResource().getSize();
te.setSize( size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size );
}
te.setMode( entry.getMode() );
PlexusIoResourceAttributes attributes = entry.getResourceAttributes();
te.setUserName( ( attributes != null && attributes.getUserName() != null )
? attributes.getUserName()
: options.getUserName() );
te.setGroupName( ( attributes != null && attributes.getGroupName() != null )
? attributes.getGroupName()
: options.getGroup() );
final int userId =
( attributes != null && attributes.getUserId() != null ) ? attributes.getUserId() : options.getUid();
if ( userId >= 0 )
{
te.setUserId( userId );
}
final int groupId =
( attributes != null && attributes.getGroupId() != null ) ? attributes.getGroupId() : options.getGid();
if ( groupId >= 0 )
{
te.setGroupId( groupId );
}
tOut.putArchiveEntry( te );
try
{
if ( entry.getResource().isFile() && !( entry.getType() == ArchiveEntry.SYMLINK ) )
{
fIn = entry.getInputStream();
Streams.copyFullyDontCloseOutput( fIn, tOut, "xAR" );
}
}
catch ( Throwable e )
{
getLogger().warn( "When creating tar entry", e );
}
finally
{
tOut.closeArchiveEntry();
}
}
finally
{
IOUtil.close( fIn );
}
}
/**
* Valid Modes for Compression attribute to Tar Task
*/
public class TarOptions
{
private String userName = "";
private String groupName = "";
private int uid;
private int gid;
private boolean preserveLeadingSlashes = false;
/**
* The username for the tar entry
* This is not the same as the UID.
*
* @param userName the user name for the tar entry.
*/
public void setUserName( String userName )
{
this.userName = userName;
}
/**
* @return the user name for the tar entry
*/
public String getUserName()
{
return userName;
}
/**
* The uid for the tar entry
* This is not the same as the User name.
*
* @param uid the id of the user for the tar entry.
*/
public void setUid( int uid )
{
this.uid = uid;
}
/**
* @return the uid for the tar entry
*/
public int getUid()
{
return uid;
}
/**
* The groupname for the tar entry; optional, default=""
* This is not the same as the GID.
*
* @param groupName the group name string.
*/
public void setGroup( String groupName )
{
this.groupName = groupName;
}
/**
* @return the group name string.
*/
public String getGroup()
{
return groupName;
}
/**
* The GID for the tar entry; optional, default="0"
* This is not the same as the group name.
*
* @param gid the group id.
*/
public void setGid( int gid )
{
this.gid = gid;
}
/**
* @return the group identifier.
*/
public int getGid()
{
return gid;
}
/**
* @return the leading slashes flag.
*/
public boolean getPreserveLeadingSlashes()
{
return preserveLeadingSlashes;
}
/**
* Flag to indicates whether leading `/'s should
* be preserved in the file names.
* Optional, default is false
.
*
* @param preserveLeadingSlashes the leading slashes flag.
*/
public void setPreserveLeadingSlashes( boolean preserveLeadingSlashes )
{
this.preserveLeadingSlashes = preserveLeadingSlashes;
}
}
/**
* Valid Modes for Compression attribute to Tar Task
*/
public static enum TarCompressionMethod
{
none,
gzip,
bzip2,
snappy,
xz
}
private OutputStream compress( TarCompressionMethod tarCompressionMethod, final OutputStream ostream )
throws IOException
{
if ( TarCompressionMethod.gzip.equals( tarCompressionMethod ) )
{
return Streams.bufferedOutputStream( new GZIPOutputStream( ostream ) );
}
else if ( TarCompressionMethod.bzip2.equals( tarCompressionMethod ) )
{
return new BZip2CompressorOutputStream( bufferedOutputStream( ostream ) );
}
else if ( TarCompressionMethod.snappy.equals( tarCompressionMethod ) )
{
return new SnappyOutputStream( bufferedOutputStream( ostream ) );
}
else if ( TarCompressionMethod.xz.equals( tarCompressionMethod ) )
{
return new XZCompressorOutputStream( bufferedOutputStream( ostream ) );
}
return ostream;
}
@Override
public boolean isSupportingForced()
{
return true;
}
@Override
protected void cleanUp()
throws IOException
{
super.cleanUp();
if ( this.tOut != null )
{
this.tOut.close();
}
}
@Override
protected void close()
throws IOException
{
if ( this.tOut != null )
{
this.tOut.close();
}
}
@Override
protected String getArchiveType()
{
return "TAR";
}
}