1 /*
   2  * Copyright (c) 2018, 2019 SAP SE. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact SAP SE, Dietmar-Hopp-Allee 16, 69190 Walldorf, Germany
  20  * or visit www.sap.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.IOException;
  25 import java.nio.file.CopyOption;
  26 import java.nio.file.DirectoryStream;
  27 import java.nio.file.FileSystem;
  28 import java.nio.file.FileVisitResult;
  29 import java.nio.file.Files;
  30 import java.nio.file.Path;
  31 import java.nio.file.Paths;
  32 import java.nio.file.SimpleFileVisitor;
  33 import java.nio.file.StandardCopyOption;
  34 import java.nio.file.attribute.BasicFileAttributes;
  35 import java.nio.file.attribute.GroupPrincipal;
  36 import java.nio.file.attribute.PosixFileAttributeView;
  37 import java.nio.file.attribute.PosixFileAttributes;
  38 import java.nio.file.attribute.PosixFilePermission;
  39 import java.nio.file.attribute.PosixFilePermissions;
  40 import java.nio.file.attribute.UserPrincipal;
  41 import java.nio.file.spi.FileSystemProvider;
  42 import java.security.AccessController;
  43 import java.security.PrivilegedAction;
  44 import java.util.Collections;
  45 import java.util.HashMap;
  46 import java.util.Map;
  47 import java.util.Set;
  48 import java.util.concurrent.atomic.AtomicInteger;
  49 
  50 import org.testng.annotations.Test;
  51 
  52 import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
  53 import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
  54 import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE;
  55 import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE;
  56 import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
  57 import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE;
  58 import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
  59 import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
  60 import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
  61 import static org.testng.Assert.assertEquals;
  62 import static org.testng.Assert.assertNotNull;
  63 import static org.testng.Assert.assertNull;
  64 import static org.testng.Assert.assertTrue;
  65 import static org.testng.Assert.fail;
  66 
  67 /**
  68  * @test
  69  * @bug 8213031
  70  * @modules jdk.zipfs
  71  * @run testng TestPosix
  72  * @run testng/othervm/java.security.policy=test.policy TestPosix
  73  * @summary Test POSIX zip file operations.
  74  */
  75 public class TestPosix {
  76     // files and directories
  77     private static final Path ZIP_FILE = Paths.get("testPosix.zip");
  78     private static final Path ZIP_FILE_COPY = Paths.get("testPosixCopy.zip");
  79     private static final Path UNZIP_DIR = Paths.get("unzip/");
  80 
  81     // permission sets
  82     private static final Set<PosixFilePermission> ALLPERMS =
  83         PosixFilePermissions.fromString("rwxrwxrwx");
  84     private static final Set<PosixFilePermission> EMPTYPERMS =
  85         Collections.<PosixFilePermission>emptySet();
  86     private static final Set<PosixFilePermission> UR = Set.of(OWNER_READ);
  87     private static final Set<PosixFilePermission> UW = Set.of(OWNER_WRITE);
  88     private static final Set<PosixFilePermission> UE = Set.of(OWNER_EXECUTE);
  89     private static final Set<PosixFilePermission> GR = Set.of(GROUP_READ);
  90     private static final Set<PosixFilePermission> GW = Set.of(GROUP_WRITE);
  91     private static final Set<PosixFilePermission> GE = Set.of(GROUP_EXECUTE);
  92     private static final Set<PosixFilePermission> OR = Set.of(OTHERS_READ);
  93     private static final Set<PosixFilePermission> OW = Set.of(OTHERS_WRITE);
  94     private static final Set<PosixFilePermission> OE = Set.of(OTHERS_EXECUTE);
  95 
  96     // principals
  97     private static final UserPrincipal DUMMY_USER = ()->"defusr";
  98     private static final GroupPrincipal DUMMY_GROUP = ()->"defgrp";
  99 
 100     // FS open options
 101     private static final Map<String, Object> ENV_DEFAULT = Collections.<String, Object>emptyMap();
 102     private static final Map<String, Object> ENV_POSIX = Map.of("enablePosixFileAttributes", true);
 103 
 104     // misc
 105     private static final FileSystemProvider zipFSP;
 106     private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES};
 107     private static final Map<String, ZipFileEntryInfo> ENTRIES = new HashMap<>();
 108 
 109     private int entriesCreated;
 110 
 111     static enum checkExpects {
 112         contentOnly,
 113         noPermDataInZip,
 114         permsInZip,
 115         permsPosix
 116     }
 117 
 118     static class ZipFileEntryInfo {
 119         // permissions to set initially
 120         private final Set<PosixFilePermission> intialPerms;
 121         // permissions to set in a later call
 122         private final Set<PosixFilePermission> laterPerms;
 123         // permissions that should be effective in the zip file
 124         private final Set<PosixFilePermission> permsInZip;
 125         // permissions that should be returned by zipfs w/Posix support
 126         private final Set<PosixFilePermission> permsPosix;
 127         // entry is a directory
 128         private final boolean isDir;
 129         // need additional read flag in copy test
 130         private final boolean setReadFlag;
 131 
 132         private ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms,
 133             Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag)
 134         {
 135             this.intialPerms = initialPerms;
 136             this.laterPerms = laterPerms;
 137             this.permsInZip = permsInZip;
 138             this.permsPosix = permsZipPosix;
 139             this.isDir = isDir;
 140             this.setReadFlag = setReadFlag;
 141         }
 142     }
 143 
 144     static class CopyVisitor extends SimpleFileVisitor<Path> {
 145         private Path from, to;
 146         private boolean copyPerms;
 147 
 148         CopyVisitor(Path from, Path to) {
 149             this.from = from;
 150             this.to = to;
 151         }
 152 
 153         CopyVisitor(Path from, Path to, boolean copyPerms) {
 154             this.from = from;
 155             this.to = to;
 156             this.copyPerms = copyPerms;
 157         }
 158 
 159         @Override
 160         public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
 161             FileVisitResult rc = super.preVisitDirectory(dir, attrs);
 162             Path target = to.resolve(from.relativize(dir).toString());
 163             if (!Files.exists(target)) {
 164                 Files.copy(dir, target, COPY_ATTRIBUTES);
 165                 if (copyPerms) {
 166                     Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(dir));
 167                 }
 168             }
 169             return rc;
 170         }
 171 
 172         @Override
 173         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 174             FileVisitResult rc = super.visitFile(file, attrs);
 175             Path target = to.resolve(from.relativize(file).toString());
 176             Files.copy(file, target, COPY_ATTRIBUTES);
 177             if (copyPerms) {
 178                 Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(file));
 179             }
 180             return rc;
 181         }
 182     }
 183 
 184     static class DeleteVisitor extends SimpleFileVisitor<Path> {
 185         @Override
 186         public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
 187             FileVisitResult rc = super.postVisitDirectory(dir, exc);
 188             Files.delete(dir);
 189             return rc;
 190         }
 191 
 192         @Override
 193         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 194             FileVisitResult rc = super.visitFile(file, attrs);
 195             Files.delete(file);
 196             return rc;
 197         }
 198     }
 199 
 200     @FunctionalInterface
 201     static interface Executor {
 202         void doIt() throws IOException;
 203     }
 204 
 205     static {
 206         zipFSP = getZipFSProvider();
 207         assertNotNull(zipFSP, "ZIP filesystem provider is not installed");
 208         ENTRIES.put("dir",        new ZipFileEntryInfo(ALLPERMS,   null, ALLPERMS,   ALLPERMS,   true,  false));
 209         ENTRIES.put("uread",      new ZipFileEntryInfo(UR,         null, UR,         UR,         false, false));
 210         ENTRIES.put("uwrite",     new ZipFileEntryInfo(UW,         null, UW,         UW,         false, true));
 211         ENTRIES.put("uexec",      new ZipFileEntryInfo(UE,         null, UE,         UE,         false, true));
 212         ENTRIES.put("gread",      new ZipFileEntryInfo(GR,         null, GR,         GR,         false, true));
 213         ENTRIES.put("gwrite",     new ZipFileEntryInfo(GW,         null, GW,         GW,         false, true));
 214         ENTRIES.put("gexec",      new ZipFileEntryInfo(GE,         null, GE,         GE,         false, true));
 215         ENTRIES.put("oread",      new ZipFileEntryInfo(OR,         null, OR,         OR,         false, true));
 216         ENTRIES.put("owrite",     new ZipFileEntryInfo(OW,         null, OW,         OW,         false, true));
 217         ENTRIES.put("oexec",      new ZipFileEntryInfo(OE,         null, OE,         OE,         false, true));
 218         ENTRIES.put("emptyperms", new ZipFileEntryInfo(EMPTYPERMS, null, EMPTYPERMS, EMPTYPERMS, false, true));
 219         ENTRIES.put("noperms",    new ZipFileEntryInfo(null,       null, null,       ALLPERMS,   false, false));
 220         ENTRIES.put("permslater", new ZipFileEntryInfo(null,       UR,   UR,         UR,         false, false));
 221     }
 222 
 223     private static String expectedDefaultOwner(Path zf) {
 224         try {
 225             return Files.getOwner(zf).getName();
 226         } catch (Exception e) {
 227             // if we can't get the owner of the file, we fall back to system property user.name
 228         }
 229         try {
 230             return AccessController.doPrivileged(
 231                 (PrivilegedAction<String>)()->System.getProperty("user.name"));
 232         } catch (Exception e) {
 233             // then we fall back to a generic default string
 234         }
 235         return "<zipfs_default>";
 236     }
 237 
 238     private static FileSystemProvider getZipFSProvider() {
 239         for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
 240             if ("jar".equals(provider.getScheme()))
 241                 return provider;
 242         }
 243         return null;
 244     }
 245 
 246     private void putEntry(FileSystem fs, String name, ZipFileEntryInfo entry) throws IOException {
 247         if (entry.isDir) {
 248             if (entry.intialPerms == null) {
 249                 Files.createDirectory(fs.getPath(name));
 250             } else {
 251                 Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
 252             }
 253 
 254         } else {
 255             if (entry.intialPerms == null) {
 256                 Files.createFile(fs.getPath(name));
 257             } else {
 258                 Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
 259             }
 260         }
 261         if (entry.laterPerms != null) {
 262             Files.setAttribute(fs.getPath(name), "zip:storedPermissions", entry.laterPerms);
 263         }
 264         entriesCreated++;
 265     }
 266 
 267     private FileSystem createTestZipFile(Path zpath, Map<String, Object> env) throws IOException {
 268         if (Files.exists(zpath)) {
 269             System.out.println("Deleting old " + zpath + "...");
 270             Files.delete(zpath);
 271         }
 272         System.out.println("Creating " + zpath + "...");
 273         entriesCreated = 0;
 274         var opts = new HashMap<String, Object>();
 275         opts.putAll(env);
 276         opts.put("create", true);
 277         FileSystem fs = zipFSP.newFileSystem(zpath, opts);
 278         for (String name : ENTRIES.keySet()) {
 279             putEntry(fs, name, ENTRIES.get(name));
 280         }
 281         return fs;
 282     }
 283 
 284     private FileSystem createEmptyZipFile(Path zpath, Map<String, Object> env) throws IOException {
 285         if (Files.exists(zpath)) {
 286             System.out.println("Deleting old " + zpath + "...");
 287             Files.delete(zpath);
 288         }
 289         System.out.println("Creating " + zpath + "...");
 290         var opts = new HashMap<String, Object>();
 291         opts.putAll(env);
 292         opts.put("create", true);
 293         return zipFSP.newFileSystem(zpath, opts);
 294     }
 295 
 296     private void delTree(Path p) throws IOException {
 297         if (Files.exists(p)) {
 298             Files.walkFileTree(p, new DeleteVisitor());
 299         }
 300     }
 301 
 302     private void addOwnerRead(Path root) throws IOException {
 303         for (String name : ENTRIES.keySet()) {
 304             ZipFileEntryInfo ei = ENTRIES.get(name);
 305             if (!ei.setReadFlag) {
 306                 continue;
 307             }
 308             Path setReadOn = root.resolve(name);
 309             Set<PosixFilePermission> perms = Files.getPosixFilePermissions(setReadOn);
 310             perms.add(OWNER_READ);
 311             Files.setPosixFilePermissions(setReadOn, perms);
 312         }
 313     }
 314 
 315     private void removeOwnerRead(Path root) throws IOException {
 316         for (String name : ENTRIES.keySet()) {
 317             ZipFileEntryInfo ei = ENTRIES.get(name);
 318             if (!ei.setReadFlag) {
 319                 continue;
 320             }
 321             Path removeReadFrom = root.resolve(name);
 322             Set<PosixFilePermission> perms = Files.getPosixFilePermissions(removeReadFrom);
 323             perms.remove(OWNER_READ);
 324             Files.setPosixFilePermissions(removeReadFrom, perms);
 325         }
 326     }
 327 
 328     @SuppressWarnings("unchecked")
 329     private void checkEntry(Path file, checkExpects expected) {
 330         System.out.println("Checking " + file + "...");
 331         String name = file.getFileName().toString();
 332         ZipFileEntryInfo ei = ENTRIES.get(name);
 333         assertNotNull(ei, "Found unknown entry " + name + ".");
 334         BasicFileAttributes attrs = null;
 335         if (expected == checkExpects.permsPosix) {
 336             try {
 337                 attrs = Files.readAttributes(file, PosixFileAttributes.class);
 338             } catch (IOException e) {
 339                 e.printStackTrace();
 340                 fail("Caught IOException reading file attributes (posix) for " + name + ": " + e.getMessage());
 341             }
 342         } else {
 343             try {
 344                 attrs = Files.readAttributes(file, BasicFileAttributes.class);
 345             } catch (IOException e) {
 346                 e.printStackTrace();
 347                 fail("Caught IOException reading file attributes (basic) " + name + ": " + e.getMessage());
 348             }
 349         }
 350         assertEquals(Files.isDirectory(file), ei.isDir, "Unexpected directory attribute for:" + System.lineSeparator() + attrs);
 351 
 352         if (expected == checkExpects.contentOnly) {
 353             return;
 354         }
 355 
 356         Set<PosixFilePermission> permissions;
 357         if (expected == checkExpects.permsPosix) {
 358             try {
 359                 permissions = Files.getPosixFilePermissions(file);
 360             } catch (IOException e) {
 361                 e.printStackTrace();
 362                 fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
 363                 return;
 364             }
 365             comparePermissions(ei.permsPosix, permissions);
 366         } else if (expected == checkExpects.permsInZip || expected == checkExpects.noPermDataInZip) {
 367             try {
 368                 permissions = (Set<PosixFilePermission>)Files.getAttribute(file, "zip:storedPermissions");
 369             } catch (IOException e) {
 370                 e.printStackTrace();
 371                 fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
 372                 return;
 373             }
 374             comparePermissions(expected == checkExpects.noPermDataInZip ? null : ei.permsInZip, permissions);
 375         }
 376     }
 377 
 378     private void doCheckEntries(Path path, checkExpects expected) throws IOException {
 379         AtomicInteger entries = new AtomicInteger();
 380 
 381         try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) {
 382             paths.forEach(file -> {
 383                 entries.getAndIncrement();
 384                 checkEntry(file, expected);
 385             });
 386         }
 387         System.out.println("Number of entries: " + entries.get() + ".");
 388         assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries.");
 389     }
 390 
 391     private void checkEntries(FileSystem fs, checkExpects expected) throws IOException {
 392         System.out.println("Checking permissions on file system " + fs + "...");
 393         doCheckEntries(fs.getPath("/"), expected);
 394     }
 395 
 396     private void checkEntries(Path path, checkExpects expected) throws IOException {
 397         System.out.println("Checking permissions on path " + path + "...");
 398         doCheckEntries(path, expected);
 399     }
 400 
 401     private boolean throwsUOE(Executor e) throws IOException {
 402         try {
 403             e.doIt();
 404             return false;
 405         } catch (UnsupportedOperationException exc) {
 406             return true;
 407         }
 408     }
 409 
 410     private void comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual) {
 411         if (expected == null) {
 412             assertNull(actual, "Permissions are not null");
 413         } else {
 414             assertNotNull(actual, "Permissions are null.");
 415             assertEquals(actual.size(), expected.size(), "Unexpected number of permissions (" +
 416                 actual.size() + " received vs " + expected.size() + " expected).");
 417             for (PosixFilePermission p : expected) {
 418                 assertTrue(actual.contains(p), "Posix permission " + p + " missing.");
 419             }
 420         }
 421     }
 422 
 423     /**
 424      * This tests whether the entries in a zip file created w/o
 425      * Posix support are correct.
 426      *
 427      * @throws IOException
 428      */
 429     @Test
 430     public void testDefault() throws IOException {
 431         // create zip file using zipfs with default options
 432         createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
 433         // check entries on zipfs with default options
 434         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
 435             checkEntries(zip, checkExpects.permsInZip);
 436         }
 437         // check entries on zipfs with posix options
 438         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE, ENV_POSIX)) {
 439             checkEntries(zip, checkExpects.permsPosix);
 440         }
 441     }
 442 
 443     /**
 444      * This tests whether the entries in a zip file created w/
 445      * Posix support are correct.
 446      *
 447      * @throws IOException
 448      */
 449     @Test
 450     public void testPosix() throws IOException {
 451         // create zip file using zipfs with posix option
 452         createTestZipFile(ZIP_FILE, ENV_POSIX).close();
 453         // check entries on zipfs with default options
 454         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
 455             checkEntries(zip, checkExpects.permsInZip);
 456         }
 457         // check entries on zipfs with posix options
 458         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE, ENV_POSIX)) {
 459             checkEntries(zip, checkExpects.permsPosix);
 460         }
 461     }
 462 
 463     /**
 464      * This tests whether the entries in a zip file copied from another
 465      * are correct.
 466      *
 467      * @throws IOException
 468      */
 469     @Test
 470     public void testCopy() throws IOException {
 471         // copy zip to zip with default options
 472         try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT);
 473              FileSystem zipOut = createEmptyZipFile(ZIP_FILE_COPY, ENV_DEFAULT)) {
 474             Path from = zipIn.getPath("/");
 475             Files.walkFileTree(from, new CopyVisitor(from, zipOut.getPath("/")));
 476         }
 477         // check entries on copied zipfs with default options
 478         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
 479             checkEntries(zip, checkExpects.permsInZip);
 480         }
 481         // check entries on copied zipfs with posix options
 482         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
 483             checkEntries(zip, checkExpects.permsPosix);
 484         }
 485     }
 486 
 487     /**
 488      * This tests whether the entries of a zip file look correct after extraction
 489      * and re-packing. When not using zipfs with Posix support, we expect the
 490      * effective permissions in the resulting zip file to be empty.
 491      *
 492      * @throws IOException
 493      */
 494     @Test
 495     public void testUnzipDefault() throws IOException {
 496         delTree(UNZIP_DIR);
 497         Files.createDirectory(UNZIP_DIR);
 498 
 499         try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
 500             Path from = srcZip.getPath("/");
 501             Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR));
 502         }
 503 
 504         // we just check that the entries got extracted to file system
 505         checkEntries(UNZIP_DIR, checkExpects.contentOnly);
 506 
 507         // the target zip file is opened with Posix support
 508         // but we expect no permission data to be copied using the default copy method
 509         try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
 510             Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/")));
 511         }
 512 
 513         // check entries on copied zipfs - no permission data should exist
 514         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
 515             checkEntries(zip, checkExpects.noPermDataInZip);
 516         }
 517     }
 518 
 519     /**
 520      * This tests whether the entries of a zip file look correct after extraction
 521      * and re-packing. If the default file system supports Posix, we test whether we
 522      * correctly carry the Posix permissions. Otherwise there's not much to test in
 523      * this method.
 524      *
 525      * @throws IOException
 526      */
 527     @Test
 528     public void testUnzipPosix() throws IOException {
 529         delTree(UNZIP_DIR);
 530         Files.createDirectory(UNZIP_DIR);
 531 
 532         try {
 533             Files.getPosixFilePermissions(UNZIP_DIR);
 534         } catch (Exception e) {
 535             // if we run into any exception here, be it because of the fact that the file system
 536             // is not Posix or if we have insufficient security permissions, we can't do this test.
 537             System.out.println("This can't be tested here because of " + e);
 538             return;
 539         }
 540 
 541         try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_POSIX)) {
 542             Path from = srcZip.getPath("/");
 543             // copy permissions as well
 544             Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR, true));
 545         }
 546 
 547         // permissions should have been propagated to file system
 548         checkEntries(UNZIP_DIR, checkExpects.permsPosix);
 549 
 550         try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
 551             // Make some files owner readable to be able to copy them into the zipfs
 552             addOwnerRead(UNZIP_DIR);
 553 
 554             // copy permissions as well
 555             Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/"), true));
 556 
 557             // Fix back all the files in the target zip file which have been made readable before
 558             removeOwnerRead(tgtZip.getPath("/"));
 559         }
 560 
 561         // check entries on copied zipfs - permission data should have been propagated
 562         try (FileSystem zip = zipFSP.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
 563             checkEntries(zip, checkExpects.permsPosix);
 564         }
 565     }
 566 
 567     /**
 568      * Tests POSIX default behavior.
 569      *
 570      * @throws IOException
 571      */
 572     @Test
 573     public void testPosixDefaults() throws IOException {
 574         // test with posix = false, expect UnsupportedOperationException
 575         try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
 576             var entry = zipIn.getPath("/dir");
 577             assertTrue(throwsUOE(()->Files.getPosixFilePermissions(entry)));
 578             assertTrue(throwsUOE(()->Files.setPosixFilePermissions(entry, UW)));
 579             assertTrue(throwsUOE(()->Files.getOwner(entry)));
 580             assertTrue(throwsUOE(()->Files.setOwner(entry, DUMMY_USER)));
 581             assertTrue(throwsUOE(()->Files.getFileAttributeView(entry, PosixFileAttributeView.class)));
 582         }
 583 
 584         // test with posix = true -> default values
 585         try (FileSystem zipIn = zipFSP.newFileSystem(ZIP_FILE, ENV_POSIX)) {
 586             String defaultOwner = expectedDefaultOwner(ZIP_FILE);
 587             var entry = zipIn.getPath("/noperms");
 588             comparePermissions(ALLPERMS, Files.getPosixFilePermissions(entry));
 589             var owner = Files.getOwner(entry);
 590             assertNotNull(owner, "owner should not be null");
 591             assertEquals(owner.getName(), defaultOwner);
 592             Files.setOwner(entry, DUMMY_USER);
 593             assertEquals(Files.getOwner(entry), DUMMY_USER);
 594             var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
 595             var group = view.readAttributes().group();
 596             assertNotNull(group, "group must not be null");
 597             assertEquals(group.getName(), defaultOwner);
 598             view.setGroup(DUMMY_GROUP);
 599             assertEquals(view.readAttributes().group(), DUMMY_GROUP);
 600             entry = zipIn.getPath("/uexec");
 601             Files.setPosixFilePermissions(entry, GR); // will be persisted
 602             comparePermissions(GR, Files.getPosixFilePermissions(entry));
 603         }
 604 
 605         // test with posix = true + custom defaults of type String
 606         try (FileSystem zipIn = zipFSP.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
 607             "defaultOwner", "auser", "defaultGroup", "agroup", "defaultPermissions", "r--------")))
 608         {
 609             var entry = zipIn.getPath("/noperms");
 610             comparePermissions(UR, Files.getPosixFilePermissions(entry));
 611             assertEquals(Files.getOwner(entry).getName(), "auser");
 612             var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
 613             assertEquals(view.readAttributes().group().getName(), "agroup");
 614             // check if the change to permissions of /uexec was persisted
 615             comparePermissions(GR, Files.getPosixFilePermissions(zipIn.getPath("/uexec")));
 616         }
 617 
 618         // test with posix = true + custom defaults as Objects
 619         try (FileSystem zipIn = zipFSP.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
 620             "defaultOwner", DUMMY_USER, "defaultGroup", DUMMY_GROUP, "defaultPermissions", UR)))
 621         {
 622             var entry = zipIn.getPath("/noperms");
 623             comparePermissions(UR, Files.getPosixFilePermissions(entry));
 624             assertEquals(Files.getOwner(entry), DUMMY_USER);
 625             var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
 626             assertEquals(view.readAttributes().group(), DUMMY_GROUP);
 627         }
 628     }
 629 }