1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.codehaus.plexus.archiver.tar;
18
19 import javax.inject.Named;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.util.zip.GZIPOutputStream;
26
27 import io.airlift.compress.snappy.SnappyFramedOutputStream;
28 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
29 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
30 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
31 import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
32 import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
33 import org.codehaus.plexus.archiver.AbstractArchiver;
34 import org.codehaus.plexus.archiver.ArchiveEntry;
35 import org.codehaus.plexus.archiver.ArchiverException;
36 import org.codehaus.plexus.archiver.ResourceIterator;
37 import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
38 import org.codehaus.plexus.archiver.util.ResourceUtils;
39 import org.codehaus.plexus.archiver.util.Streams;
40 import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
41 import org.codehaus.plexus.components.io.functions.SymlinkDestinationSupplier;
42 import org.codehaus.plexus.components.io.resources.PlexusIoResource;
43 import org.codehaus.plexus.util.IOUtil;
44 import org.codehaus.plexus.util.StringUtils;
45
46 import static org.codehaus.plexus.archiver.util.Streams.bufferedOutputStream;
47
48
49
50
51 @Named("tar")
52 public class TarArchiver extends AbstractArchiver {
53
54
55
56
57 private boolean longWarningGiven = false;
58
59 private TarLongFileMode longFileMode = TarLongFileMode.warn;
60
61 private TarCompressionMethod compression = TarCompressionMethod.none;
62
63 private final TarOptions options = new TarOptions();
64
65 private TarArchiveOutputStream tOut;
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public void setLongfile(TarLongFileMode mode) {
85 this.longFileMode = mode;
86 }
87
88
89
90
91
92
93
94
95
96
97
98
99 public void setCompression(TarCompressionMethod mode) {
100 this.compression = mode;
101 }
102
103 @Override
104 protected void execute() throws ArchiverException, IOException {
105 if (!checkForced()) {
106 return;
107 }
108
109 ResourceIterator iter = getResources();
110 if (!iter.hasNext()) {
111 throw new EmptyArchiveException("archive cannot be empty");
112 }
113
114 File tarFile = getDestFile();
115
116 if (tarFile == null) {
117 throw new ArchiverException("You must set the destination tar file.");
118 }
119 if (tarFile.exists() && !tarFile.isFile()) {
120 throw new ArchiverException(tarFile + " isn't a file.");
121 }
122 if (tarFile.exists() && !tarFile.canWrite()) {
123 throw new ArchiverException(tarFile + " is read-only.");
124 }
125
126 getLogger().info("Building tar: " + tarFile.getAbsolutePath());
127
128 try {
129 tOut = new TarArchiveOutputStream(compress(compression, Streams.fileOutputStream(tarFile)), "UTF8");
130 if (longFileMode.isTruncateMode()) {
131 tOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE);
132 } else if (longFileMode.isPosixMode() || longFileMode.isPosixWarnMode()) {
133 tOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
134
135 tOut.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
136 } else if (longFileMode.isFailMode() || longFileMode.isOmitMode()) {
137 tOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR);
138 } else {
139
140 tOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
141 }
142
143 longWarningGiven = false;
144 while (iter.hasNext()) {
145 ArchiveEntry entry = iter.next();
146
147 if (ResourceUtils.isSame(entry.getResource(), tarFile)) {
148 throw new ArchiverException("A tar file cannot include itself.");
149 }
150 String fileName = entry.getName();
151 String name = StringUtils.replace(fileName, File.separatorChar, '/');
152
153 tarFile(entry, tOut, name);
154 }
155
156 tOut.close();
157 } finally {
158 IOUtil.close(tOut);
159 }
160 }
161
162
163
164
165
166
167
168
169
170
171 protected void tarFile(ArchiveEntry entry, TarArchiveOutputStream tOut, String vPath)
172 throws ArchiverException, IOException {
173
174
175 if (vPath.length() <= 0) {
176 return;
177 }
178
179 if (entry.getResource().isDirectory() && !vPath.endsWith("/")) {
180 vPath += "/";
181 }
182
183 if (vPath.startsWith("/") && !options.getPreserveLeadingSlashes()) {
184 int l = vPath.length();
185 if (l <= 1) {
186
187 return;
188 }
189 vPath = vPath.substring(1, l);
190 }
191
192 int pathLength = vPath.length();
193 InputStream fIn = null;
194
195 try {
196 TarArchiveEntry te;
197 if (!longFileMode.isGnuMode()
198 && pathLength >= org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN) {
199 int maxPosixPathLen = org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
200 + org.apache.commons.compress.archivers.tar.TarConstants.PREFIXLEN;
201 if (longFileMode.isPosixMode()) {
202 } else if (longFileMode.isPosixWarnMode()) {
203 if (pathLength > maxPosixPathLen) {
204 getLogger().warn("Entry: " + vPath + " longer than " + maxPosixPathLen + " characters.");
205 if (!longWarningGiven) {
206 getLogger()
207 .warn("Resulting tar file can only be processed "
208 + "successfully by GNU compatible tar commands");
209 longWarningGiven = true;
210 }
211 }
212 } else if (longFileMode.isOmitMode()) {
213 getLogger().info("Omitting: " + vPath);
214 return;
215 } else if (longFileMode.isWarnMode()) {
216 getLogger()
217 .warn("Entry: " + vPath + " longer than "
218 + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
219 + " characters.");
220 if (!longWarningGiven) {
221 getLogger()
222 .warn("Resulting tar file can only be processed "
223 + "successfully by GNU compatible tar commands");
224 longWarningGiven = true;
225 }
226 } else if (longFileMode.isFailMode()) {
227 throw new ArchiverException("Entry: " + vPath + " longer than "
228 + org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN
229 + " characters.");
230 } else {
231 throw new IllegalStateException("Non gnu mode should never get here?");
232 }
233 }
234
235 if (entry.getType() == ArchiveEntry.SYMLINK) {
236 final SymlinkDestinationSupplier plexusIoSymlinkResource =
237 (SymlinkDestinationSupplier) entry.getResource();
238
239 te = new TarArchiveEntry(vPath, TarArchiveEntry.LF_SYMLINK);
240 te.setLinkName(plexusIoSymlinkResource.getSymlinkDestination());
241 } else {
242 te = new TarArchiveEntry(vPath);
243 }
244
245 if (getLastModifiedTime() == null) {
246 long teLastModified = entry.getResource().getLastModified();
247 te.setModTime(
248 teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE
249 ? System.currentTimeMillis()
250 : teLastModified);
251 } else {
252 te.setModTime(getLastModifiedTime().toMillis());
253 }
254
255 if (entry.getType() == ArchiveEntry.SYMLINK) {
256 te.setSize(0);
257
258 } else if (!entry.getResource().isDirectory()) {
259 final long size = entry.getResource().getSize();
260 te.setSize(size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size);
261 }
262 te.setMode(entry.getMode());
263
264 PlexusIoResourceAttributes attributes = entry.getResourceAttributes();
265
266 te.setUserName(
267 (attributes != null && attributes.getUserName() != null)
268 ? attributes.getUserName()
269 : options.getUserName());
270 te.setGroupName(
271 (attributes != null && attributes.getGroupName() != null)
272 ? attributes.getGroupName()
273 : options.getGroup());
274
275 final int userId =
276 (attributes != null && attributes.getUserId() != null) ? attributes.getUserId() : options.getUid();
277 if (userId >= 0) {
278 te.setUserId(userId);
279 }
280
281 final int groupId = (attributes != null && attributes.getGroupId() != null)
282 ? attributes.getGroupId()
283 : options.getGid();
284 if (groupId >= 0) {
285 te.setGroupId(groupId);
286 }
287
288 tOut.putArchiveEntry(te);
289
290 try {
291 if (entry.getResource().isFile() && !(entry.getType() == ArchiveEntry.SYMLINK)) {
292 fIn = entry.getInputStream();
293
294 Streams.copyFullyDontCloseOutput(fIn, tOut, "xAR");
295 }
296
297 } catch (Throwable e) {
298 getLogger().warn("When creating tar entry", e);
299 } finally {
300 tOut.closeArchiveEntry();
301 }
302 } finally {
303 IOUtil.close(fIn);
304 }
305 }
306
307
308
309
310 public class TarOptions {
311
312 private String userName = "";
313
314 private String groupName = "";
315
316 private int uid;
317
318 private int gid;
319
320 private boolean preserveLeadingSlashes = false;
321
322
323
324
325
326
327
328 public void setUserName(String userName) {
329 this.userName = userName;
330 }
331
332
333
334
335 public String getUserName() {
336 return userName;
337 }
338
339
340
341
342
343
344
345 public void setUid(int uid) {
346 this.uid = uid;
347 }
348
349
350
351
352 public int getUid() {
353 return uid;
354 }
355
356
357
358
359
360
361
362 public void setGroup(String groupName) {
363 this.groupName = groupName;
364 }
365
366
367
368
369 public String getGroup() {
370 return groupName;
371 }
372
373
374
375
376
377
378
379 public void setGid(int gid) {
380 this.gid = gid;
381 }
382
383
384
385
386 public int getGid() {
387 return gid;
388 }
389
390
391
392
393 public boolean getPreserveLeadingSlashes() {
394 return preserveLeadingSlashes;
395 }
396
397
398
399
400
401
402
403
404 public void setPreserveLeadingSlashes(boolean preserveLeadingSlashes) {
405 this.preserveLeadingSlashes = preserveLeadingSlashes;
406 }
407 }
408
409
410
411
412 public enum TarCompressionMethod {
413 none,
414 gzip,
415 bzip2,
416 snappy,
417 xz,
418 zstd
419 }
420
421 private OutputStream compress(TarCompressionMethod tarCompressionMethod, final OutputStream ostream)
422 throws IOException {
423 if (TarCompressionMethod.gzip.equals(tarCompressionMethod)) {
424 return bufferedOutputStream(new GZIPOutputStream(ostream));
425 } else if (TarCompressionMethod.bzip2.equals(tarCompressionMethod)) {
426 return new BZip2CompressorOutputStream(bufferedOutputStream(ostream));
427 } else if (TarCompressionMethod.snappy.equals(tarCompressionMethod)) {
428 return new SnappyFramedOutputStream(bufferedOutputStream(ostream));
429 } else if (TarCompressionMethod.xz.equals(tarCompressionMethod)) {
430 return new XZCompressorOutputStream(bufferedOutputStream(ostream));
431 } else if (TarCompressionMethod.zstd.equals(tarCompressionMethod)) {
432 return new ZstdCompressorOutputStream(bufferedOutputStream(ostream));
433 }
434
435 return ostream;
436 }
437
438 @Override
439 public boolean isSupportingForced() {
440 return true;
441 }
442
443 @Override
444 protected void cleanUp() throws IOException {
445 super.cleanUp();
446 if (this.tOut != null) {
447 this.tOut.close();
448 }
449 }
450
451 @Override
452 protected void close() throws IOException {
453 if (this.tOut != null) {
454 this.tOut.close();
455 }
456 }
457
458 @Override
459 protected String getArchiveType() {
460 return "TAR";
461 }
462 }