-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TAR: Implement extraction and archiving of hardlinks. #286
base: master
Are you sure you want to change the base?
Changes from 1 commit
913783b
95c0f44
dd5e14f
cea72cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,10 @@ | |
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.BasicFileAttributeView; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.zip.GZIPOutputStream; | ||
|
||
import org.apache.commons.compress.archivers.tar.TarArchiveEntry; | ||
|
@@ -39,6 +43,7 @@ | |
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.PlexusIoFileResource; | ||
import org.codehaus.plexus.components.io.resources.PlexusIoResource; | ||
import org.codehaus.plexus.util.IOUtil; | ||
import org.codehaus.plexus.util.StringUtils; | ||
|
@@ -65,6 +70,8 @@ public class TarArchiver extends AbstractArchiver { | |
|
||
private TarArchiveOutputStream tOut; | ||
|
||
private final Map<Object, String> seenFiles = new HashMap<>(10); | ||
|
||
/** | ||
* Set how to handle long files, those with a path>100 chars. | ||
* Optional, default=warn. | ||
|
@@ -177,7 +184,8 @@ protected void tarFile(ArchiveEntry entry, TarArchiveOutputStream tOut, String v | |
return; | ||
} | ||
|
||
if (entry.getResource().isDirectory() && !vPath.endsWith("/")) { | ||
final PlexusIoResource ioResource = entry.getResource(); | ||
if (ioResource.isDirectory() && !vPath.endsWith("/")) { | ||
vPath += "/"; | ||
} | ||
|
||
|
@@ -194,7 +202,7 @@ protected void tarFile(ArchiveEntry entry, TarArchiveOutputStream tOut, String v | |
InputStream fIn = null; | ||
|
||
try { | ||
TarArchiveEntry te; | ||
TarArchiveEntry te = null; | ||
if (!longFileMode.isGnuMode() | ||
&& pathLength >= org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN) { | ||
int maxPosixPathLen = org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN | ||
|
@@ -233,18 +241,43 @@ protected void tarFile(ArchiveEntry entry, TarArchiveOutputStream tOut, String v | |
} | ||
} | ||
|
||
boolean doCopy = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found this name confusing. |
||
if (entry.getType() == ArchiveEntry.SYMLINK) { | ||
final SymlinkDestinationSupplier plexusIoSymlinkResource = | ||
(SymlinkDestinationSupplier) entry.getResource(); | ||
final SymlinkDestinationSupplier plexusIoSymlinkResource = (SymlinkDestinationSupplier) ioResource; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this change related to hard links? It seems that it is just refactoring to avoid calling |
||
|
||
te = new TarArchiveEntry(vPath, TarArchiveEntry.LF_SYMLINK); | ||
te.setLinkName(plexusIoSymlinkResource.getSymlinkDestination()); | ||
} else { | ||
doCopy = false; | ||
} else if (options.getPreserveHardLinks() | ||
&& ioResource.isFile() | ||
&& ioResource instanceof PlexusIoFileResource) { | ||
final PlexusIoFileResource fileResource = (PlexusIoFileResource) ioResource; | ||
final Path file = fileResource.getFile().toPath(); | ||
if (Files.exists(file)) { | ||
final BasicFileAttributeView fileAttributeView = | ||
Files.getFileAttributeView(file, BasicFileAttributeView.class); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather use |
||
if (fileAttributeView != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be best to extract this check to separate method to make this method easier to read. |
||
final Object fileKey = | ||
fileAttributeView.readAttributes().fileKey(); | ||
if (fileKey != null) { | ||
final String seenFile = this.seenFiles.get(fileKey); | ||
if (seenFile != null) { | ||
te = new TarArchiveEntry(vPath, TarArchiveEntry.LF_LINK); | ||
te.setLinkName(seenFile); | ||
doCopy = false; | ||
} else { | ||
this.seenFiles.put(fileKey, vPath); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (te == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest I don't follow why this is added. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
te = new TarArchiveEntry(vPath); | ||
} | ||
|
||
if (getLastModifiedTime() == null) { | ||
long teLastModified = entry.getResource().getLastModified(); | ||
long teLastModified = ioResource.getLastModified(); | ||
te.setModTime( | ||
teLastModified == PlexusIoResource.UNKNOWN_MODIFICATION_DATE | ||
? System.currentTimeMillis() | ||
|
@@ -253,11 +286,11 @@ protected void tarFile(ArchiveEntry entry, TarArchiveOutputStream tOut, String v | |
te.setModTime(getLastModifiedTime().toMillis()); | ||
} | ||
|
||
if (entry.getType() == ArchiveEntry.SYMLINK) { | ||
if (!doCopy) { | ||
te.setSize(0); | ||
|
||
} else if (!entry.getResource().isDirectory()) { | ||
final long size = entry.getResource().getSize(); | ||
} else if (!ioResource.isDirectory()) { | ||
final long size = ioResource.getSize(); | ||
te.setSize(size == PlexusIoResource.UNKNOWN_RESOURCE_SIZE ? 0 : size); | ||
} | ||
te.setMode(entry.getMode()); | ||
|
@@ -289,7 +322,7 @@ protected void tarFile(ArchiveEntry entry, TarArchiveOutputStream tOut, String v | |
tOut.putArchiveEntry(te); | ||
|
||
try { | ||
if (entry.getResource().isFile() && !(entry.getType() == ArchiveEntry.SYMLINK)) { | ||
if (ioResource.isFile() && doCopy) { | ||
fIn = entry.getInputStream(); | ||
|
||
Streams.copyFullyDontCloseOutput(fIn, tOut, "xAR"); | ||
|
@@ -320,6 +353,8 @@ public class TarOptions { | |
|
||
private boolean preserveLeadingSlashes = false; | ||
|
||
private boolean preserveHardLinks = true; | ||
|
||
/** | ||
* The username for the tar entry | ||
* This is not the same as the UID. | ||
|
@@ -405,6 +440,14 @@ public boolean getPreserveLeadingSlashes() { | |
public void setPreserveLeadingSlashes(boolean preserveLeadingSlashes) { | ||
this.preserveLeadingSlashes = preserveLeadingSlashes; | ||
} | ||
|
||
public boolean getPreserveHardLinks() { | ||
return preserveHardLinks; | ||
} | ||
|
||
public void setPreserveHardLinks(boolean preserveHardLinks) { | ||
this.preserveHardLinks = preserveHardLinks; | ||
} | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package org.codehaus.plexus.archiver; | ||
|
||
import java.io.File; | ||
import java.nio.file.Files; | ||
|
||
import org.codehaus.plexus.archiver.tar.TarArchiver; | ||
import org.codehaus.plexus.archiver.tar.TarLongFileMode; | ||
import org.codehaus.plexus.archiver.tar.TarUnArchiver; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.condition.DisabledOnOs; | ||
import org.junit.jupiter.api.condition.OS; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
/** | ||
* @author Kristian Rosenvold | ||
*/ | ||
public class HardlinkTest extends TestSupport { | ||
|
||
@Test | ||
@DisabledOnOs(OS.WINDOWS) | ||
public void testHardlinkTar() throws Exception { | ||
// Extract test files | ||
final File archiveFile = getTestFile("src/test/resources/hardlinks/hardlinks.tar"); | ||
File output = getTestFile("target/output/untaredHardlinks"); | ||
output.mkdirs(); | ||
TarUnArchiver unarchiver = (TarUnArchiver) lookup(UnArchiver.class, "tar"); | ||
unarchiver.setSourceFile(archiveFile); | ||
unarchiver.setDestFile(output); | ||
unarchiver.extract(); | ||
// Check that we have hardlinks | ||
assertTrue(Files.isSameFile( | ||
output.toPath().resolve("fileR.txt"), output.toPath().resolve("hardlink"))); | ||
|
||
// Archive the extracted hardlinks to new archive | ||
TarArchiver archiver = (TarArchiver) lookup(Archiver.class, "tar"); | ||
archiver.setLongfile(TarLongFileMode.posix); | ||
archiver.addDirectory(output); | ||
final File testFile = getTestFile("target/output/untaredHardlinks2.tar"); | ||
archiver.setDestFile(testFile); | ||
archiver.createArchive(); | ||
|
||
// Check that our created archive actually contains hardlinks when extracted | ||
unarchiver = (TarUnArchiver) lookup(UnArchiver.class, "tar"); | ||
output = getTestFile("target/output/untaredHardlinks2"); | ||
output.mkdirs(); | ||
unarchiver.setSourceFile(testFile); | ||
unarchiver.setDestFile(output); | ||
unarchiver.extract(); | ||
assertTrue(Files.isSameFile( | ||
output.toPath().resolve("fileR.txt"), output.toPath().resolve("hardlink"))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is breaking change. I wonder if we can avoid it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how.