src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
changeset 59216 47c879f478d2
parent 59112 fe87a92570db
equal deleted inserted replaced
59215:fcd74557a9cc 59216:47c879f478d2
    31 import java.io.EOFException;
    31 import java.io.EOFException;
    32 import java.io.FilterOutputStream;
    32 import java.io.FilterOutputStream;
    33 import java.io.IOException;
    33 import java.io.IOException;
    34 import java.io.InputStream;
    34 import java.io.InputStream;
    35 import java.io.OutputStream;
    35 import java.io.OutputStream;
       
    36 import java.lang.Runtime.Version;
    36 import java.nio.ByteBuffer;
    37 import java.nio.ByteBuffer;
    37 import java.nio.MappedByteBuffer;
    38 import java.nio.MappedByteBuffer;
    38 import java.nio.channels.FileChannel;
    39 import java.nio.channels.FileChannel;
    39 import java.nio.channels.FileLock;
    40 import java.nio.channels.FileLock;
    40 import java.nio.channels.ReadableByteChannel;
    41 import java.nio.channels.ReadableByteChannel;
    48 import java.security.PrivilegedActionException;
    49 import java.security.PrivilegedActionException;
    49 import java.security.PrivilegedExceptionAction;
    50 import java.security.PrivilegedExceptionAction;
    50 import java.util.*;
    51 import java.util.*;
    51 import java.util.concurrent.locks.ReadWriteLock;
    52 import java.util.concurrent.locks.ReadWriteLock;
    52 import java.util.concurrent.locks.ReentrantReadWriteLock;
    53 import java.util.concurrent.locks.ReentrantReadWriteLock;
       
    54 import java.util.function.Consumer;
       
    55 import java.util.function.Function;
       
    56 import java.util.jar.Attributes;
       
    57 import java.util.jar.Manifest;
    53 import java.util.regex.Pattern;
    58 import java.util.regex.Pattern;
    54 import java.util.zip.CRC32;
    59 import java.util.zip.CRC32;
    55 import java.util.zip.Deflater;
    60 import java.util.zip.Deflater;
    56 import java.util.zip.DeflaterOutputStream;
    61 import java.util.zip.DeflaterOutputStream;
    57 import java.util.zip.Inflater;
    62 import java.util.zip.Inflater;
    83     private static final byte[] ROOTPATH = new byte[] { '/' };
    88     private static final byte[] ROOTPATH = new byte[] { '/' };
    84     private static final String PROPERTY_POSIX = "enablePosixFileAttributes";
    89     private static final String PROPERTY_POSIX = "enablePosixFileAttributes";
    85     private static final String PROPERTY_DEFAULT_OWNER = "defaultOwner";
    90     private static final String PROPERTY_DEFAULT_OWNER = "defaultOwner";
    86     private static final String PROPERTY_DEFAULT_GROUP = "defaultGroup";
    91     private static final String PROPERTY_DEFAULT_GROUP = "defaultGroup";
    87     private static final String PROPERTY_DEFAULT_PERMISSIONS = "defaultPermissions";
    92     private static final String PROPERTY_DEFAULT_PERMISSIONS = "defaultPermissions";
       
    93     // Property used to specify the entry version to use for a multi-release JAR
       
    94     private static final String PROPERTY_RELEASE_VERSION = "releaseVersion";
       
    95     // Original property used to specify the entry version to use for a
       
    96     // multi-release JAR which is kept for backwards compatibility.
       
    97     private static final String PROPERTY_MULTI_RELEASE = "multi-release";
    88 
    98 
    89     private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS =
    99     private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS =
    90         PosixFilePermissions.fromString("rwxrwxrwx");
   100         PosixFilePermissions.fromString("rwxrwxrwx");
    91     // Property used to specify the compression mode to use
   101     // Property used to specify the compression mode to use
    92     private static final String PROPERTY_COMPRESSION_METHOD = "compressionMethod";
   102     private static final String PROPERTY_COMPRESSION_METHOD = "compressionMethod";
   109     private final boolean useTempFile;   // use a temp file for newOS, default
   119     private final boolean useTempFile;   // use a temp file for newOS, default
   110                                          // is to use BAOS for better performance
   120                                          // is to use BAOS for better performance
   111     private final boolean forceEnd64;
   121     private final boolean forceEnd64;
   112     private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true"
   122     private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true"
   113                                                 // METHOD_DEFLATED otherwise
   123                                                 // METHOD_DEFLATED otherwise
       
   124 
       
   125     // entryLookup is identity by default, will be overridden for multi-release jars
       
   126     private Function<byte[], byte[]> entryLookup = Function.identity();
   114 
   127 
   115     // POSIX support
   128     // POSIX support
   116     final boolean supportPosix;
   129     final boolean supportPosix;
   117     private final UserPrincipal defaultOwner;
   130     private final UserPrincipal defaultOwner;
   118     private final GroupPrincipal defaultGroup;
   131     private final GroupPrincipal defaultGroup;
   165             }
   178             }
   166             throw x;
   179             throw x;
   167         }
   180         }
   168         this.provider = provider;
   181         this.provider = provider;
   169         this.zfpath = zfpath;
   182         this.zfpath = zfpath;
       
   183 
       
   184         initializeReleaseVersion(env);
   170     }
   185     }
   171 
   186 
   172     /**
   187     /**
   173      * Return the compression method to use (STORED or DEFLATED).  If the
   188      * Return the compression method to use (STORED or DEFLATED).  If the
   174      * property {@code commpressionMethod} is set use its value to determine
   189      * property {@code commpressionMethod} is set use its value to determine
  1347                 }
  1362                 }
  1348             }
  1363             }
  1349         }
  1364         }
  1350     }
  1365     }
  1351 
  1366 
       
  1367     /**
       
  1368      * If a version property has been specified and the file represents a multi-release JAR,
       
  1369      * determine the requested runtime version and initialize the ZipFileSystem instance accordingly.
       
  1370      *
       
  1371      * Checks if the Zip File System property "releaseVersion" has been specified. If it has,
       
  1372      * use its value to determine the requested version. If not use the value of the "multi-release" property.
       
  1373      */
       
  1374     private void initializeReleaseVersion(Map<String, ?> env) throws IOException {
       
  1375         Object o = env.containsKey(PROPERTY_RELEASE_VERSION) ?
       
  1376             env.get(PROPERTY_RELEASE_VERSION) :
       
  1377             env.get(PROPERTY_MULTI_RELEASE);
       
  1378 
       
  1379         if (o != null && isMultiReleaseJar()) {
       
  1380             int version;
       
  1381             if (o instanceof String) {
       
  1382                 String s = (String)o;
       
  1383                 if (s.equals("runtime")) {
       
  1384                     version = Runtime.version().feature();
       
  1385                 } else if (s.matches("^[1-9][0-9]*$")) {
       
  1386                     version = Version.parse(s).feature();
       
  1387                 } else {
       
  1388                     throw new IllegalArgumentException("Invalid runtime version");
       
  1389                 }
       
  1390             } else if (o instanceof Integer) {
       
  1391                 version = Version.parse(((Integer)o).toString()).feature();
       
  1392             } else if (o instanceof Version) {
       
  1393                 version = ((Version)o).feature();
       
  1394             } else {
       
  1395                 throw new IllegalArgumentException("env parameter must be String, " +
       
  1396                     "Integer, or Version");
       
  1397             }
       
  1398             createVersionedLinks(version < 0 ? 0 : version);
       
  1399             setReadOnly();
       
  1400         }
       
  1401     }
       
  1402 
       
  1403     /**
       
  1404      * Returns true if the Manifest main attribute "Multi-Release" is set to true; false otherwise.
       
  1405      */
       
  1406     private boolean isMultiReleaseJar() throws IOException {
       
  1407         try (InputStream is = newInputStream(getBytes("/META-INF/MANIFEST.MF"))) {
       
  1408             String multiRelease = new Manifest(is).getMainAttributes()
       
  1409                 .getValue(Attributes.Name.MULTI_RELEASE);
       
  1410             return "true".equalsIgnoreCase(multiRelease);
       
  1411         } catch (NoSuchFileException x) {
       
  1412             return false;
       
  1413         }
       
  1414     }
       
  1415 
       
  1416     /**
       
  1417      * Create a map of aliases for versioned entries, for example:
       
  1418      *   version/PackagePrivate.class -> META-INF/versions/9/version/PackagePrivate.class
       
  1419      *   version/PackagePrivate.java -> META-INF/versions/9/version/PackagePrivate.java
       
  1420      *   version/Version.class -> META-INF/versions/10/version/Version.class
       
  1421      *   version/Version.java -> META-INF/versions/10/version/Version.java
       
  1422      *
       
  1423      * Then wrap the map in a function that getEntry can use to override root
       
  1424      * entry lookup for entries that have corresponding versioned entries.
       
  1425      */
       
  1426     private void createVersionedLinks(int version) {
       
  1427         IndexNode verdir = getInode(getBytes("/META-INF/versions"));
       
  1428         // nothing to do, if no /META-INF/versions
       
  1429         if (verdir == null) {
       
  1430             return;
       
  1431         }
       
  1432         // otherwise, create a map and for each META-INF/versions/{n} directory
       
  1433         // put all the leaf inodes, i.e. entries, into the alias map
       
  1434         // possibly shadowing lower versioned entries
       
  1435         HashMap<IndexNode, byte[]> aliasMap = new HashMap<>();
       
  1436         getVersionMap(version, verdir).values().forEach(versionNode ->
       
  1437             walk(versionNode.child, entryNode ->
       
  1438                 aliasMap.put(
       
  1439                     getOrCreateInode(getRootName(entryNode, versionNode), entryNode.isdir),
       
  1440                     entryNode.name))
       
  1441         );
       
  1442         entryLookup = path -> {
       
  1443             byte[] entry = aliasMap.get(IndexNode.keyOf(path));
       
  1444             return entry == null ? path : entry;
       
  1445         };
       
  1446     }
       
  1447 
       
  1448     /**
       
  1449      * Create a sorted version map of version -> inode, for inodes <= max version.
       
  1450      *   9 -> META-INF/versions/9
       
  1451      *  10 -> META-INF/versions/10
       
  1452      */
       
  1453     private TreeMap<Integer, IndexNode> getVersionMap(int version, IndexNode metaInfVersions) {
       
  1454         TreeMap<Integer,IndexNode> map = new TreeMap<>();
       
  1455         IndexNode child = metaInfVersions.child;
       
  1456         while (child != null) {
       
  1457             Integer key = getVersion(child, metaInfVersions);
       
  1458             if (key != null && key <= version) {
       
  1459                 map.put(key, child);
       
  1460             }
       
  1461             child = child.sibling;
       
  1462         }
       
  1463         return map;
       
  1464     }
       
  1465 
       
  1466     /**
       
  1467      * Extract the integer version number -- META-INF/versions/9 returns 9.
       
  1468      */
       
  1469     private Integer getVersion(IndexNode inode, IndexNode metaInfVersions) {
       
  1470         try {
       
  1471             byte[] fullName = inode.name;
       
  1472             return Integer.parseInt(getString(Arrays
       
  1473                 .copyOfRange(fullName, metaInfVersions.name.length + 1, fullName.length)));
       
  1474         } catch (NumberFormatException x) {
       
  1475             // ignore this even though it might indicate issues with the JAR structure
       
  1476             return null;
       
  1477         }
       
  1478     }
       
  1479 
       
  1480     /**
       
  1481      * Walk the IndexNode tree processing all leaf nodes.
       
  1482      */
       
  1483     private void walk(IndexNode inode, Consumer<IndexNode> consumer) {
       
  1484         if (inode == null) return;
       
  1485         if (inode.isDir()) {
       
  1486             walk(inode.child, consumer);
       
  1487         } else {
       
  1488             consumer.accept(inode);
       
  1489         }
       
  1490         walk(inode.sibling, consumer);
       
  1491     }
       
  1492 
       
  1493     /**
       
  1494      * Extract the root name from a versioned entry name.
       
  1495      * E.g. given inode 'META-INF/versions/9/foo/bar.class'
       
  1496      * and prefix 'META-INF/versions/9/' returns 'foo/bar.class'.
       
  1497      */
       
  1498     private byte[] getRootName(IndexNode inode, IndexNode prefix) {
       
  1499         byte[] fullName = inode.name;
       
  1500         return Arrays.copyOfRange(fullName, prefix.name.length, fullName.length);
       
  1501     }
       
  1502 
  1352     // Reads zip file central directory. Returns the file position of first
  1503     // Reads zip file central directory. Returns the file position of first
  1353     // CEN header, otherwise returns -1 if an error occurred. If zip->msg != NULL
  1504     // CEN header, otherwise returns -1 if an error occurred. If zip->msg != NULL
  1354     // then the error was a zip format error and zip->msg has the error text.
  1505     // then the error was a zip format error and zip->msg has the error text.
  1355     // Always pass in -1 for knownTotal; it's used for a recursive call.
  1506     // Always pass in -1 for knownTotal; it's used for a recursive call.
  1356     private byte[] initCEN() throws IOException {
  1507     private byte[] initCEN() throws IOException {
  1642 
  1793 
  1643         Files.move(tmpFile, zfpath, REPLACE_EXISTING);
  1794         Files.move(tmpFile, zfpath, REPLACE_EXISTING);
  1644         hasUpdate = false;    // clear
  1795         hasUpdate = false;    // clear
  1645     }
  1796     }
  1646 
  1797 
  1647     IndexNode getInode(byte[] path) {
  1798     private IndexNode getInode(byte[] path) {
  1648         return inodes.get(IndexNode.keyOf(Objects.requireNonNull(path, "path")));
  1799         return inodes.get(IndexNode.keyOf(Objects.requireNonNull(entryLookup.apply(path), "path")));
  1649     }
  1800     }
  1650 
  1801 
  1651     /**
  1802     /**
  1652      * Return the IndexNode from the root tree. If it doesn't exist,
  1803      * Return the IndexNode from the root tree. If it doesn't exist,
  1653      * it gets created along with all parent directory IndexNodes.
  1804      * it gets created along with all parent directory IndexNodes.
  1654      */
  1805      */
  1655     IndexNode getOrCreateInode(byte[] path, boolean isdir) {
  1806     private IndexNode getOrCreateInode(byte[] path, boolean isdir) {
  1656         IndexNode node = getInode(path);
  1807         IndexNode node = getInode(path);
  1657         // if node exists, return it
  1808         // if node exists, return it
  1658         if (node != null) {
  1809         if (node != null) {
  1659             return node;
  1810             return node;
  1660         }
  1811         }
  2246             return (toPos == to.length) ? to : Arrays.copyOf(to, toPos);
  2397             return (toPos == to.length) ? to : Arrays.copyOf(to, toPos);
  2247         }
  2398         }
  2248 
  2399 
  2249         private static final ThreadLocal<IndexNode> cachedKey = new ThreadLocal<>();
  2400         private static final ThreadLocal<IndexNode> cachedKey = new ThreadLocal<>();
  2250 
  2401 
  2251         final static IndexNode keyOf(byte[] name) { // get a lookup key;
  2402         static final IndexNode keyOf(byte[] name) { // get a lookup key;
  2252             IndexNode key = cachedKey.get();
  2403             IndexNode key = cachedKey.get();
  2253             if (key == null) {
  2404             if (key == null) {
  2254                 key = new IndexNode(name, -1);
  2405                 key = new IndexNode(name, -1);
  2255                 cachedKey.set(key);
  2406                 cachedKey.set(key);
  2256             }
  2407             }