package org.codehaus.plexus.archiver.tar;
/**
*
* 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.
*/
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
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.UnixStat;
import org.codehaus.plexus.archiver.util.EnumeratedAttribute;
import org.codehaus.plexus.archiver.util.ResourceUtils;
import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
import org.codehaus.plexus.components.io.resources.PlexusIoResource;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import java.io.BufferedOutputStream;
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;
/**
* @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
* @version $Revision$ $Date$
*/
public class TarArchiver
extends AbstractArchiver
{
/**
* Indicates whether the user has been warned about long files already.
*/
private boolean longWarningGiven = false;
private TarLongFileMode longFileMode = new TarLongFileMode();
private TarCompressionMethod compression = new TarCompressionMethod();
private TarOptions options = new TarOptions();
private TarOutputStream tOut;
/**
*
*/
public TarOptions getOptions()
{
return options;
}
/**
* Set all tar options
*
* @param options options
*/
public void setOptions( TarOptions options )
{
this.options = options;
// FIXME: do these options have precedence over
// setDefaultFileMode / setDefaultDirMode
// or the other way around? Assuming these
// take precedende since they're more specific.
// Better refactor this when usage is known.
setDefaultFileMode( options.getMode() );
setDefaultDirectoryMode( options.getMode() );
}
// /**
// * Override AbstractArchiver.setDefaultFileMode to
// * update TarOptions.
// */
// public void setDefaultFileMode( int mode )
// {
// super.setDefaultFileMode( mode );
//
// options.setMode( mode );
// }
//
// /**
// * Override AbstractArchiver.setDefaultDirectoryMode to
// * update TarOptions.
// */
// public void setDefaultDirectoryMode( int mode )
// {
// super.setDefaultDirectoryMode( mode );
//
// options.setDirMode( mode );
// }
//
//
/**
* Set how to handle long files, those with a path>100 chars.
* Optional, default=warn.
* <p/>
* Allowable values are
* <ul>
* <li> truncate - paths are truncated to the maximum length
* <li> fail - paths greater than the maximum cause a build exception
* <li> warn - paths greater than the maximum cause a warning and GNU is used
* <li> gnu - GNU extensions are used for any paths greater than the maximum.
* <li> posix - posix extensions are used for any paths greater than the maximum.
* <li> posixwarn - posix extensions are used (with warning) for any paths greater than the maximum.
* <li> omit - paths greater than the maximum are omitted from the archive
* </ul>
*
* @param mode the mode to handle long file names.
*/
public void setLongfile( TarLongFileMode mode )
{
this.longFileMode = mode;
}
/**
* Set compression method.
* Allowable values are
* <ul>
* <li> none - no compression
* <li> gzip - Gzip compression
* <li> bzip2 - Bzip2 compression
* </ul>
*
* @param mode the compression method.
*/
public void setCompression( TarCompressionMethod mode )
{
this.compression = mode;
}
protected void execute()
throws ArchiverException, IOException
{
if ( !checkForced() )
{
return;
}
ResourceIterator iter = getResources();
if ( !iter.hasNext() )
{
throw new ArchiverException( "You must set at least one file." );
}
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() );
tOut = new TarOutputStream(
compression.compress( new BufferedOutputStream( new FileOutputStream( tarFile ) ) ) );
tOut.setDebug( true );
if ( longFileMode.isTruncateMode() )
{
tOut.setLongFileMode( TarOutputStream.LONGFILE_TRUNCATE );
}
else if ( longFileMode.isFailMode() || longFileMode.isOmitMode() )
{
tOut.setLongFileMode( TarOutputStream.LONGFILE_ERROR );
}
else
{
// warn or GNU
tOut.setLongFileMode( TarOutputStream.LONGFILE_GNU );
}
longWarningGiven = false;
while ( iter.hasNext() )
{
ArchiveEntry entry = iter.next();
// Check if we don't add tar file in inself
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 );
}
}
/**
* 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, TarOutputStream tOut, String vPath )
throws ArchiverException, IOException
{
InputStream fIn = null;
// 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();
try
{
final TarEntry te;
if ( !longFileMode.isGnuMode() && pathLength >= TarConstants.NAMELEN )
{
int maxPosixPathLen = TarConstants.NAMELEN + TarConstants.POSIX_PREFIXLEN;
if ( longFileMode.isPosixMode() )
{
if ( pathLength > maxPosixPathLen )
{
te = new TarEntry( vPath );
}
else
{
te = new PosixTarEntry( vPath );
}
}
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;
}
te = new TarEntry( vPath );
}
else
{
te = new PosixTarEntry( vPath );
}
}
else if ( longFileMode.isOmitMode() )
{
getLogger().info( "Omitting: " + vPath );
return;
}
else if ( longFileMode.isWarnMode() )
{
getLogger().warn( "Entry: " + vPath + " longer than " + TarConstants.NAMELEN + " characters." );
if ( !longWarningGiven )
{
getLogger().warn( "Resulting tar file can only be processed "
+ "successfully by GNU compatible tar commands" );
longWarningGiven = true;
}
te = new TarEntry( vPath );
}
else if ( longFileMode.isFailMode() )
{
throw new ArchiverException( "Entry: " + vPath + " longer than " + TarConstants.NAMELEN
+ " characters." );
}
else
{
throw new IllegalStateException("Non gnu mode should never get here?");
}
}
else
{
/* Did not touch it, because this would change the following default tar format, however
* GNU tar can untar POSIX tar, so I think we should us PosixTarEntry here instead. */
te = new TarEntry( vPath );
}
long teLastModified = entry.getResource().getLastModified();
te.setModTime( teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE ? System.currentTimeMillis()
: teLastModified );
if ( !entry.getResource().isDirectory() )
{
final long size = entry.getResource().getSize();
te.setSize( size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size );
te.setMode( entry.getMode() );
}
else
{
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.putNextEntry( te );
if ( !entry.getResource().isDirectory() )
{
fIn = entry.getInputStream();
byte[] buffer = new byte[8 * 1024];
int count = 0;
do
{
tOut.write( buffer, 0, count );
count = fIn.read( buffer, 0, buffer.length );
}
while ( count != -1 );
}
tOut.closeEntry();
}
finally
{
IOUtil.close( fIn );
}
}
/**
* Valid Modes for Compression attribute to Tar Task
*/
public class TarOptions
{
/**
* @deprecated
*/
private int fileMode = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM;
/**
* @deprecated
*/
private int dirMode = UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM;
private String userName = "";
private String groupName = "";
private int uid;
private int gid;
private boolean preserveLeadingSlashes = false;
/**
* A 3 digit octal string, specify the user, group and
* other modes in the standard Unix fashion;
* optional, default=0644
*
* @param octalString a 3 digit octal string.
* @deprecated use AbstractArchiver.setDefaultFileMode(int)
*/
public void setMode( String octalString )
{
setMode( Integer.parseInt( octalString, 8 ) );
}
/**
* @param mode unix file mode
* @deprecated use AbstractArchiver.setDefaultFileMode(int)
*/
public void setMode( int mode )
{
this.fileMode = UnixStat.FILE_FLAG | ( mode & UnixStat.PERM_MASK );
}
/**
* @return the current mode.
* @deprecated use AbstractArchiver.getDefaultFileMode()
*/
public int getMode()
{
return fileMode;
}
/**
* A 3 digit octal string, specify the user, group and
* other modes in the standard Unix fashion;
* optional, default=0755
*
* @param octalString a 3 digit octal string.
* @since Ant 1.6
* @deprecated use AbstractArchiver.setDefaultDirectoryMode(int)
*/
public void setDirMode( String octalString )
{
setDirMode( Integer.parseInt( octalString, 8 ) );
}
/**
* @param mode unix directory mode
* @deprecated use AbstractArchiver.setDefaultDirectoryMode(int)
*/
public void setDirMode( int mode )
{
this.dirMode = UnixStat.DIR_FLAG | ( mode & UnixStat.PERM_MASK );
}
/**
* @return the current directory mode
* @since Ant 1.6
* @deprecated use AbstractArchiver.getDefaultDirectoryMode()
*/
public int getDirMode()
{
return dirMode;
}
/**
* 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 <code>false</code>.
*
* @param preserveLeadingSlashes the leading slashes flag.
*/
public void setPreserveLeadingSlashes( boolean preserveLeadingSlashes )
{
this.preserveLeadingSlashes = preserveLeadingSlashes;
}
}
/**
* Valid Modes for Compression attribute to Tar Task
*/
public static final class TarCompressionMethod
extends EnumeratedAttribute
{
// permissible values for compression attribute
/**
* No compression
*/
private static final String NONE = "none";
/**
* GZIP compression
*/
private static final String GZIP = "gzip";
/**
* BZIP2 compression
*/
private static final String BZIP2 = "bzip2";
/**
* Default constructor
*/
public TarCompressionMethod()
{
super();
try
{
setValue( NONE );
}
catch ( ArchiverException ae )
{
//Do nothing
}
}
/**
* Get valid enumeration values.
*
* @return valid enumeration values
*/
public String[] getValues()
{
return new String[]{NONE, GZIP, BZIP2};
}
/**
* This method wraps the output stream with the
* corresponding compression method
*
* @param ostream output stream
* @return output stream with on-the-fly compression
* @throws IOException thrown if file is not writable
*/
private OutputStream compress( final OutputStream ostream )
throws IOException
{
final String value = getValue();
if ( GZIP.equals( value ) )
{
return new GZIPOutputStream( ostream );
}
else if ( BZIP2.equals( value ) )
{
ostream.write( 'B' );
ostream.write( 'Z' );
return new BZip2CompressorOutputStream( ostream );
}
return ostream;
}
}
public boolean isSupportingForced()
{
return true;
}
protected void cleanUp()
{
super.cleanUp();
tOut = null;
}
protected void close()
throws IOException
{
IOUtil.close( tOut );
}
protected String getArchiveType()
{
return "TAR";
}
}