View Javadoc
1   /**
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  public class TarArchiver
48      extends AbstractArchiver
49  {
50  
51      /**
52       * Indicates whether the user has been warned about long files already.
53       */
54      private boolean longWarningGiven = false;
55  
56      private TarLongFileMode longFileMode = TarLongFileMode.warn;
57  
58      private TarCompressionMethod compression = TarCompressionMethod.none;
59  
60      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 </p>
69       * <ul>
70       * <li> truncate - paths are truncated to the maximum length </li>
71       * <li> fail - paths greater than the maximum cause a build exception </li>
72       * <li> warn - paths greater than the maximum cause a warning and GNU is used </li>
73       * <li> gnu - GNU extensions are used for any paths greater than the maximum. </li>
74       * <li> posix - posix extensions are used for any paths greater than the maximum. </li>
75       * <li> posixwarn - posix extensions are used (with warning) for any paths greater than the maximum. </li>
76       * <li> omit - paths greater than the maximum are omitted from the archive </li>
77       * </ul>
78       *
79       * @param mode the mode to handle long file names.
80       */
81      public void setLongfile( TarLongFileMode mode )
82      {
83          this.longFileMode = mode;
84      }
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          this.compression = mode;
100     }
101 
102     @Override
103     protected void execute()
104         throws ArchiverException, IOException
105     {
106         if ( !checkForced() )
107         {
108             return;
109         }
110 
111         ResourceIterator iter = getResources();
112         if ( !iter.hasNext() )
113         {
114             throw new EmptyArchiveException( "archive cannot be empty" );
115         }
116 
117         File tarFile = getDestFile();
118 
119         if ( tarFile == null )
120         {
121             throw new ArchiverException( "You must set the destination tar file." );
122         }
123         if ( tarFile.exists() && !tarFile.isFile() )
124         {
125             throw new ArchiverException( tarFile + " isn't a file." );
126         }
127         if ( tarFile.exists() && !tarFile.canWrite() )
128         {
129             throw new ArchiverException( tarFile + " is read-only." );
130         }
131 
132         getLogger().info( "Building tar: " + tarFile.getAbsolutePath() );
133 
134         try
135         {
136             tOut = new TarArchiveOutputStream( compress( compression, new FileOutputStream( tarFile ) ), "UTF8" );
137             if ( longFileMode.isTruncateMode() )
138             {
139                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_TRUNCATE );
140             }
141             else if ( longFileMode.isPosixMode() || longFileMode.isPosixWarnMode() )
142             {
143                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
144                 // Todo: Patch 2.5.1   for this fix. Also make closeable fix on 2.5.1
145                 tOut.setBigNumberMode( TarArchiveOutputStream.BIGNUMBER_POSIX );
146             }
147             else if ( longFileMode.isFailMode() || longFileMode.isOmitMode() )
148             {
149                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_ERROR );
150             }
151             else
152             {
153                 // warn or GNU
154                 tOut.setLongFileMode( TarArchiveOutputStream.LONGFILE_GNU );
155             }
156 
157             longWarningGiven = false;
158             while ( iter.hasNext() )
159             {
160                 ArchiveEntry entry = iter.next();
161                 // Check if we don't add tar file in itself
162                 if ( ResourceUtils.isSame( entry.getResource(), tarFile ) )
163                 {
164                     throw new ArchiverException( "A tar file cannot include itself." );
165                 }
166                 String fileName = entry.getName();
167                 String name = StringUtils.replace( fileName, File.separatorChar, '/' );
168 
169                 tarFile( entry, tOut, name );
170             }
171 
172             tOut.close();
173         }
174         finally
175         {
176             IOUtil.close( tOut );
177         }
178     }
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         if ( vPath.length() <= 0 )
195         {
196             return;
197         }
198 
199         if ( entry.getResource().isDirectory() && !vPath.endsWith( "/" ) )
200         {
201             vPath += "/";
202         }
203 
204         if ( vPath.startsWith( "/" ) && !options.getPreserveLeadingSlashes() )
205         {
206             int l = vPath.length();
207             if ( l <= 1 )
208             {
209                 // we would end up adding "" to the archive
210                 return;
211             }
212             vPath = vPath.substring( 1, l );
213         }
214 
215         int pathLength = vPath.length();
216         InputStream fIn = null;
217 
218         try
219         {
220             TarArchiveEntry te;
221             if ( !longFileMode.isGnuMode()
222                      && pathLength >= org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN )
223             {
224                 int maxPosixPathLen = org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
225                                           + org.apache.commons.compress.archivers.tar.TarConstants.PREFIXLEN;
226                 if ( longFileMode.isPosixMode() )
227                 {
228                 }
229                 else if ( longFileMode.isPosixWarnMode() )
230                 {
231                     if ( pathLength > maxPosixPathLen )
232                     {
233                         getLogger().warn( "Entry: " + vPath + " longer than " + maxPosixPathLen + " characters." );
234                         if ( !longWarningGiven )
235                         {
236                             getLogger().warn( "Resulting tar file can only be processed "
237                                                   + "successfully by GNU compatible tar commands" );
238                             longWarningGiven = true;
239                         }
240                     }
241                 }
242                 else if ( longFileMode.isOmitMode() )
243                 {
244                     getLogger().info( "Omitting: " + vPath );
245                     return;
246                 }
247                 else if ( longFileMode.isWarnMode() )
248                 {
249                     getLogger().warn( "Entry: " + vPath + " longer than "
250                                           + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
251                                           + " characters." );
252                     if ( !longWarningGiven )
253                     {
254                         getLogger().warn( "Resulting tar file can only be processed "
255                                               + "successfully by GNU compatible tar commands" );
256                         longWarningGiven = true;
257                     }
258                 }
259                 else if ( longFileMode.isFailMode() )
260                 {
261                     throw new ArchiverException( "Entry: " + vPath + " longer than "
262                                                      + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
263                                                      + " characters." );
264                 }
265                 else
266                 {
267                     throw new IllegalStateException( "Non gnu mode should never get here?" );
268                 }
269             }
270 
271             if ( entry.getType() == ArchiveEntry.SYMLINK )
272             {
273                 final SymlinkDestinationSupplier plexusIoSymlinkResource =
274                     (SymlinkDestinationSupplier) entry.getResource();
275 
276                 te = new TarArchiveEntry( vPath, TarArchiveEntry.LF_SYMLINK );
277                 te.setLinkName( plexusIoSymlinkResource.getSymlinkDestination() );
278             }
279             else
280             {
281                 te = new TarArchiveEntry( vPath );
282             }
283 
284             long teLastModified = entry.getResource().getLastModified();
285             te.setModTime( teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
286                                ? System.currentTimeMillis()
287                                : teLastModified );
288 
289             if ( entry.getType() == ArchiveEntry.SYMLINK )
290             {
291                 te.setSize( 0 );
292 
293             }
294             else if ( !entry.getResource().isDirectory() )
295             {
296                 final long size = entry.getResource().getSize();
297                 te.setSize( size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size );
298             }
299             te.setMode( entry.getMode() );
300 
301             PlexusIoResourceAttributes attributes = entry.getResourceAttributes();
302 
303             te.setUserName( ( attributes != null && attributes.getUserName() != null )
304                                 ? attributes.getUserName()
305                                 : options.getUserName() );
306             te.setGroupName( ( attributes != null && attributes.getGroupName() != null )
307                                  ? attributes.getGroupName()
308                                  : options.getGroup() );
309 
310             final int userId =
311                 ( attributes != null && attributes.getUserId() != null ) ? attributes.getUserId() : options.getUid();
312             if ( userId >= 0 )
313             {
314                 te.setUserId( userId );
315             }
316 
317             final int groupId =
318                 ( attributes != null && attributes.getGroupId() != null ) ? attributes.getGroupId() : options.getGid();
319             if ( groupId >= 0 )
320             {
321                 te.setGroupId( groupId );
322             }
323 
324             tOut.putArchiveEntry( te );
325 
326             try
327             {
328                 if ( entry.getResource().isFile() && !( entry.getType() == ArchiveEntry.SYMLINK ) )
329                 {
330                     fIn = entry.getInputStream();
331 
332                     Streams.copyFullyDontCloseOutput( fIn, tOut, "xAR" );
333                 }
334 
335             }
336             catch ( Throwable e )
337             {
338                 getLogger().warn( "When creating tar entry", e );
339             }
340             finally
341             {
342                 tOut.closeArchiveEntry();
343             }
344         }
345         finally
346         {
347             IOUtil.close( fIn );
348         }
349     }
350 
351     /**
352      * Valid Modes for Compression attribute to Tar Task
353      */
354     public class TarOptions
355     {
356 
357         private String userName = "";
358 
359         private String groupName = "";
360 
361         private int uid;
362 
363         private int gid;
364 
365         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             this.userName = userName;
376         }
377 
378         /**
379          * @return the user name for the tar entry
380          */
381         public String getUserName()
382         {
383             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             this.uid = uid;
395         }
396 
397         /**
398          * @return the uid for the tar entry
399          */
400         public int getUid()
401         {
402             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             this.groupName = groupName;
414         }
415 
416         /**
417          * @return the group name string.
418          */
419         public String getGroup()
420         {
421             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             this.gid = gid;
433         }
434 
435         /**
436          * @return the group identifier.
437          */
438         public int getGid()
439         {
440             return gid;
441         }
442 
443         /**
444          * @return the leading slashes flag.
445          */
446         public boolean getPreserveLeadingSlashes()
447         {
448             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             this.preserveLeadingSlashes = preserveLeadingSlashes;
461         }
462 
463     }
464 
465     /**
466      * Valid Modes for Compression attribute to Tar Task
467      */
468     public static enum TarCompressionMethod
469     {
470 
471         none,
472         gzip,
473         bzip2,
474         snappy,
475         xz
476 
477     }
478 
479     private OutputStream compress( TarCompressionMethod tarCompressionMethod, final OutputStream ostream )
480         throws IOException
481     {
482         if ( TarCompressionMethod.gzip.equals( tarCompressionMethod ) )
483         {
484             return Streams.bufferedOutputStream( new GZIPOutputStream( ostream ) );
485         }
486         else if ( TarCompressionMethod.bzip2.equals( tarCompressionMethod ) )
487         {
488             return new BZip2CompressorOutputStream( bufferedOutputStream( ostream ) );
489         }
490         else if ( TarCompressionMethod.snappy.equals( tarCompressionMethod ) )
491         {
492             return new SnappyOutputStream( bufferedOutputStream( ostream ) );
493         }
494         else if ( TarCompressionMethod.xz.equals( tarCompressionMethod ) )
495         {
496             return new XZCompressorOutputStream( bufferedOutputStream( ostream ) );
497         }
498 
499         return ostream;
500     }
501 
502     @Override
503     public boolean isSupportingForced()
504     {
505         return true;
506     }
507 
508     @Override
509     protected void cleanUp()
510         throws IOException
511     {
512         super.cleanUp();
513         if ( this.tOut != null )
514         {
515             this.tOut.close();
516         }
517     }
518 
519     @Override
520     protected void close()
521         throws IOException
522     {
523         if ( this.tOut != null )
524         {
525             this.tOut.close();
526         }
527     }
528 
529     @Override
530     protected String getArchiveType()
531     {
532         return "TAR";
533     }
534 
535 }