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