Skip to content

Commit e180912

Browse files
authored
#132: untar preserves unix file permissios (#185)
1 parent 4327271 commit e180912

File tree

7 files changed

+140
-6
lines changed

7 files changed

+140
-6
lines changed

cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public interface FileAccess {
6262
* Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows
6363
* junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must
6464
* point to absolute paths. Therefore, the created link will be absolute instead of relative.
65-
*
65+
*
6666
* @param source the source {@link Path} to link to, may be relative or absolute.
6767
* @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
6868
* @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
@@ -73,7 +73,7 @@ public interface FileAccess {
7373
* Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a
7474
* Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback,
7575
* which must point to absolute paths. Therefore, the created link will be absolute instead of relative.
76-
*
76+
*
7777
* @param source the source {@link Path} to link to, may be relative or absolute.
7878
* @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
7979
*/

cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java

+38-3
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,23 @@
1818
import java.nio.file.Path;
1919
import java.nio.file.Paths;
2020
import java.nio.file.attribute.BasicFileAttributes;
21+
import java.nio.file.attribute.PosixFilePermission;
22+
import java.nio.file.attribute.PosixFilePermissions;
2123
import java.security.DigestInputStream;
2224
import java.security.MessageDigest;
2325
import java.security.NoSuchAlgorithmException;
2426
import java.time.LocalDateTime;
2527
import java.util.ArrayList;
2628
import java.util.Iterator;
2729
import java.util.List;
30+
import java.util.Set;
2831
import java.util.function.Function;
2932
import java.util.function.Predicate;
3033
import java.util.stream.Stream;
3134

3235
import org.apache.commons.compress.archivers.ArchiveEntry;
3336
import org.apache.commons.compress.archivers.ArchiveInputStream;
37+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
3438
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
3539
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
3640

@@ -315,8 +319,7 @@ private void deleteLinkIfExists(Path path) throws IOException {
315319
return;
316320
}
317321
}
318-
exists = exists || Files.exists(path); // "||" since broken junctions are not detected by
319-
// Files.exists(brokenJunction)
322+
exists = exists || Files.exists(path);
320323
boolean isSymlink = exists && Files.isSymbolicLink(path);
321324

322325
assert !(isSymlink && isJunction);
@@ -378,7 +381,7 @@ private Path adaptPath(Path source, Path targetLink, boolean relative) throws IO
378381

379382
/**
380383
* Creates a Windows junction at {@code targetLink} pointing to {@code source}.
381-
*
384+
*
382385
* @param source must be another Windows junction or a directory.
383386
* @param targetLink the location of the Windows junction.
384387
*/
@@ -495,12 +498,40 @@ public void untar(Path file, Path targetDir, TarCompression compression) {
495498
unpack(file, targetDir, in -> new TarArchiveInputStream(compression.unpack(in)));
496499
}
497500

501+
/**
502+
* @param permissions The integer as returned by {@link TarArchiveEntry#getMode()} that represents the file
503+
* permissions of a file on a Unix file system.
504+
* @return A String representing the file permissions. E.g. "rwxrwxr-x" or "rw-rw-r--"
505+
*/
506+
public static String generatePermissionString(int permissions) {
507+
508+
// Ensure that only the last 9 bits are considered
509+
permissions &= 0b111111111;
510+
511+
StringBuilder permissionStringBuilder = new StringBuilder("rwxrwxrwx");
512+
513+
for (int i = 0; i < 9; i++) {
514+
int mask = 1 << i;
515+
char currentChar = ((permissions & mask) != 0) ? permissionStringBuilder.charAt(8 - i) : '-';
516+
permissionStringBuilder.setCharAt(8 - i, currentChar);
517+
}
518+
519+
return permissionStringBuilder.toString();
520+
}
521+
498522
private void unpack(Path file, Path targetDir, Function<InputStream, ArchiveInputStream> unpacker) {
499523

500524
this.context.trace("Unpacking archive {} to {}", file, targetDir);
501525
try (InputStream is = Files.newInputStream(file); ArchiveInputStream ais = unpacker.apply(is)) {
502526
ArchiveEntry entry = ais.getNextEntry();
527+
boolean isTar = ais instanceof TarArchiveInputStream;
503528
while (entry != null) {
529+
String permissionStr = null;
530+
if (isTar) {
531+
int tarMode = ((TarArchiveEntry) entry).getMode();
532+
permissionStr = generatePermissionString(tarMode);
533+
}
534+
504535
Path entryName = Paths.get(entry.getName());
505536
Path entryPath = targetDir.resolve(entryName).toAbsolutePath();
506537
if (!entryPath.startsWith(targetDir)) {
@@ -513,6 +544,10 @@ private void unpack(Path file, Path targetDir, Function<InputStream, ArchiveInpu
513544
mkdirs(entryPath.getParent());
514545
Files.copy(ais, entryPath);
515546
}
547+
if (isTar) {
548+
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString(permissionStr);
549+
Files.setPosixFilePermissions(entryPath, permissions);
550+
}
516551
entry = ais.getNextEntry();
517552
}
518553
} catch (IOException e) {

cli/src/test/java/com/devonfw/tools/ide/io/FileAccessImplTest.java

+100-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package com.devonfw.tools.ide.io;
22

3+
import static com.devonfw.tools.ide.io.FileAccessImpl.generatePermissionString;
34
import static org.junit.jupiter.api.Assertions.assertThrows;
45

56
import java.io.IOException;
67
import java.nio.file.Files;
78
import java.nio.file.LinkOption;
89
import java.nio.file.NoSuchFileException;
910
import java.nio.file.Path;
11+
import java.nio.file.attribute.PosixFilePermission;
12+
import java.nio.file.attribute.PosixFilePermissions;
13+
import java.util.Set;
1014

1115
import org.junit.jupiter.api.Test;
1216
import org.junit.jupiter.api.io.TempDir;
@@ -325,7 +329,7 @@ private void createSymlinks(FileAccess fa, Path dir, boolean relative) {
325329

326330
/**
327331
* Checks if the symlinks exist. This is used by the tests of {@link FileAccessImpl#symlink(Path, Path, boolean)}.
328-
*
332+
*
329333
* @param dir the {@link Path} to the directory where the symlinks are expected.
330334
*/
331335
private void assertSymlinksExist(Path dir) {
@@ -469,4 +473,99 @@ private void assertSymlinkRead(Path link, Path trueTarget) {
469473
+ " and readPath " + readPath, e);
470474
}
471475
}
476+
477+
/**
478+
* Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#NONE} and checks if
479+
* file permissions are preserved on Unix.
480+
*/
481+
@Test
482+
public void testUntarWithNoneCompressionWithFilePermissions(@TempDir Path tempDir) {
483+
484+
// arrange
485+
IdeContext context = IdeTestContextMock.get();
486+
if (context.getSystemInfo().isWindows()) {
487+
return;
488+
}
489+
490+
// act
491+
context.getFileAccess().untar(
492+
Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar"), tempDir,
493+
TarCompression.NONE);
494+
495+
// assert
496+
assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
497+
assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
498+
}
499+
500+
/**
501+
* Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#GZ} and checks if file
502+
* permissions are preserved on Unix.
503+
*/
504+
@Test
505+
public void testUntarWithGzCompressionWithFilePermissions(@TempDir Path tempDir) {
506+
507+
// arrange
508+
IdeContext context = IdeTestContextMock.get();
509+
if (context.getSystemInfo().isWindows()) {
510+
return;
511+
}
512+
513+
// act
514+
context.getFileAccess().untar(
515+
Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.gz"), tempDir,
516+
TarCompression.GZ);
517+
518+
// assert
519+
assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
520+
assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
521+
}
522+
523+
/**
524+
* Test of {@link FileAccessImpl#untar(Path, Path, TarCompression)} with {@link TarCompression#BZIP2} and checks if
525+
* file permissions are preserved on Unix.
526+
*/
527+
@Test
528+
public void testUntarWithBzip2CompressionWithFilePermissions(@TempDir Path tempDir) {
529+
530+
// arrange
531+
IdeContext context = IdeTestContextMock.get();
532+
if (context.getSystemInfo().isWindows()) {
533+
return;
534+
}
535+
536+
// act
537+
context.getFileAccess().untar(
538+
Path.of("src/test/resources/com/devonfw/tools/ide/io").resolve("executable_and_non_executable.tar.bz2"),
539+
tempDir, TarCompression.BZIP2);
540+
541+
// assert
542+
assertPosixFilePermissions(tempDir.resolve("executableFile.txt"), "rwxrwxr-x");
543+
assertPosixFilePermissions(tempDir.resolve("nonExecutableFile.txt"), "rw-rw-r--");
544+
}
545+
546+
private void assertPosixFilePermissions(Path file, String permissions) {
547+
548+
try {
549+
Set<PosixFilePermission> posixPermissions = Files.getPosixFilePermissions(file);
550+
String permissionStr = PosixFilePermissions.toString(posixPermissions);
551+
assertThat(permissions).isEqualTo(permissionStr);
552+
} catch (IOException e) {
553+
throw new RuntimeException(e);
554+
}
555+
}
556+
557+
/**
558+
* Test of {@link FileAccessImpl#generatePermissionString(int)}.
559+
*/
560+
@Test
561+
public void testGeneratePermissionString() {
562+
563+
assertThat(generatePermissionString(0)).isEqualTo("---------");
564+
assertThat(generatePermissionString(436)).isEqualTo("rw-rw-r--");
565+
assertThat(generatePermissionString(948)).isEqualTo("rw-rw-r--");
566+
assertThat(generatePermissionString(509)).isEqualTo("rwxrwxr-x");
567+
assertThat(generatePermissionString(511)).isEqualTo("rwxrwxrwx");
568+
569+
}
570+
472571
}
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)