< prev index next >

src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java

Print this page
rev 51866 : 6194856: Zip Files lose ALL ownership and permissions of the files

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -23,53 +23,73 @@
  * questions.
  */
 
 package jdk.nio.zipfs;
 
+import static java.lang.Boolean.TRUE;
+import static jdk.nio.zipfs.ZipConstants.*;
+import static jdk.nio.zipfs.ZipUtils.*;
+import static java.nio.file.StandardOpenOption.*;
+import static java.nio.file.StandardCopyOption.*;
+
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
-import java.io.File;
 import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.MappedByteBuffer;
-import java.nio.channels.*;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.channels.WritableByteChannel;
 import java.nio.file.*;
-import java.nio.file.attribute.*;
-import java.nio.file.spi.*;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.regex.Pattern;
 import java.util.zip.CRC32;
-import java.util.zip.Inflater;
 import java.util.zip.Deflater;
-import java.util.zip.InflaterInputStream;
 import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
 import java.util.zip.ZipException;
-import static java.lang.Boolean.*;
-import static jdk.nio.zipfs.ZipConstants.*;
-import static jdk.nio.zipfs.ZipUtils.*;
-import static java.nio.file.StandardOpenOption.*;
-import static java.nio.file.StandardCopyOption.*;
 
 /**
  * A FileSystem built on a zip file
  *
  * @author Xueming Shen
  */
-
 class ZipFileSystem extends FileSystem {
-
+    private static final int FILE_ATTRIBUTES_UNIX = 3;
+    private static final int VERSION_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8;
     private final ZipFileSystemProvider provider;
     private final Path zfpath;
     final ZipCoder zc;
     private final ZipPath rootdir;
     private boolean readOnly = false;    // readonly file system

@@ -433,11 +453,11 @@
         try {
             ensureOpen();
             if (dir.length == 0 || exists(dir))  // root dir, or exiting dir
                 throw new FileAlreadyExistsException(getString(dir));
             checkParents(dir);
-            Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
+            Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
             update(e);
         } finally {
             endWrite();
         }
     }

@@ -655,11 +675,11 @@
                 }
                 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
                     throw new NoSuchFileException(getString(path));
                 checkParents(path);
                 return new EntryOutputChannel(
-                    new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));
+                    new Entry(path, Entry.NEW, false, getCompressMethod(attrs), attrs));
 
             } finally {
                 endRead();
             }
         } else {

@@ -720,11 +740,11 @@
             final boolean isFCH = (e != null && e.type == Entry.FILECH);
             final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
             final FileChannel fch = tmpfile.getFileSystem()
                                            .provider()
                                            .newFileChannel(tmpfile, options, attrs);
-            final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
+            final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH, attrs);
             if (forWrite) {
                 u.flag = FLAG_DATADESCR;
                 u.method = getCompressMethod(attrs);
             }
             // is there a better way to hook into the FileChannel's close method?

@@ -1466,11 +1486,11 @@
             // FILECH result is un-compressed.
             eis = Files.newInputStream(e.file);
             // TBD: wrap to hook close()
             // streams.add(eis);
             return eis;
-        } else {  // untouced  CEN or COPY
+        } else {  // untouched  CEN or COPY
             eis = new EntryInputStream(e, ch);
         }
         if (e.method == METHOD_DEFLATED) {
             // MORE: Compute good size for inflater stream:
             long bufSize = e.size + 2; // Inflater likes a bit of slack

@@ -1528,18 +1548,16 @@
     private class EntryInputStream extends InputStream {
         private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
                                           // point to a new channel after sync()
         private   long pos;               // current position within entry data
         protected long rem;               // number of remaining bytes within entry
-        protected final long size;        // uncompressed size of this entry
 
         EntryInputStream(Entry e, SeekableByteChannel zfch)
             throws IOException
         {
             this.zfch = zfch;
             rem = e.csize;
-            size = e.size;
             pos = e.locoff;
             if (pos == -1) {
                 Entry e2 = getEntry(e.name);
                 if (e2 == null) {
                     throw new ZipException("invalid loc for entry <" + e.name + ">");

@@ -1602,14 +1620,10 @@
 
         public int available() {
             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
         }
 
-        public long size() {
-            return size;
-        }
-
         public void close() {
             rem = 0;
             streams.remove(this);
         }
 

@@ -1661,11 +1675,11 @@
     }
 
     // List of available Deflater objects for compression
     private final List<Deflater> deflaters = new ArrayList<>();
 
-    // Gets an deflater from the list of available deflaters or allocates
+    // Gets a deflater from the list of available deflaters or allocates
     // a new one.
     private Deflater getDeflater() {
         synchronized (deflaters) {
             int size = deflaters.size();
             if (size > 0) {

@@ -1675,22 +1689,10 @@
                 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
             }
         }
     }
 
-    // Releases the specified inflater to the list of available inflaters.
-    private void releaseDeflater(Deflater def) {
-        synchronized (deflaters) {
-            if (inflaters.size() < MAX_FLATER) {
-               def.reset();
-               deflaters.add(def);
-            } else {
-               def.end();
-            }
-        }
-    }
-
     // End of central directory record
     static class END {
         // these 2 fields are not used by anyone and write() uses "0"
         // int  disknum;
         // int  sdisknum;

@@ -1854,10 +1856,11 @@
         int    type = CEN;            // default is the entry read from cen
 
         // entry attributes
         int    version;
         int    flag;
+        int    posixPerms = -1; // posix permissions
         int    method = -1;    // compression method
         long   mtime  = -1;    // last modification time (in DOS time)
         long   atime  = -1;    // last access time
         long   ctime  = -1;    // create time
         long   crc    = -1;    // crc-32 of entry data

@@ -1865,11 +1868,11 @@
         long   size   = -1;    // uncompressed size of entry data
         byte[] extra;
 
         // cen
 
-        // these fields are not used by anyone and writeCEN uses "0"
+        // these fields are not used
         // int    versionMade;
         // int    disk;
         // int    attrs;
         // long   attrsEx;
         long   locoff;

@@ -1885,16 +1888,23 @@
             this.size   = 0;
             this.csize  = 0;
             this.method = method;
         }
 
-        Entry(byte[] name, int type, boolean isdir, int method) {
+        @SuppressWarnings("unchecked")
+        Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
             this(name, isdir, method);
             this.type = type;
+            for (FileAttribute<?> attr: attrs) {
+                String attrName = attr.name();
+                if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) {
+                    posixPerms = PosixFilePermissions.toFlags((Set<PosixFilePermission>)attr.value());
+                }
+            }
         }
 
-        Entry (Entry e, int type) {
+        Entry(Entry e, int type) {
             name(e.name);
             this.isdir     = e.isdir;
             this.version   = e.version;
             this.ctime     = e.ctime;
             this.atime     = e.atime;

@@ -1910,26 +1920,46 @@
             this.attrs     = e.attrs;
             this.attrsEx   = e.attrsEx;
             */
             this.locoff    = e.locoff;
             this.comment   = e.comment;
+            this.posixPerms = e.posixPerms;
             this.type      = type;
         }
 
-        Entry (byte[] name, Path file, int type) {
+        @SuppressWarnings("unchecked")
+        Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
             this(name, type, false, METHOD_STORED);
             this.file = file;
+            for (FileAttribute<?> attr: attrs) {
+                String attrName = attr.name();
+                if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) {
+                    posixPerms = PosixFilePermissions.toFlags((Set<PosixFilePermission>)attr.value());
+                }
+            }
         }
 
-        int version() throws ZipException {
+        int version(boolean zip64) throws ZipException {
+            if (zip64) {
+                return 45;
+            }
             if (method == METHOD_DEFLATED)
                 return 20;
             else if (method == METHOD_STORED)
                 return 10;
             throw new ZipException("unsupported compression method");
         }
 
+        /**
+         * Adds information about compatibility of file attribute information
+         * to a version value.
+         */
+        int versionMadeBy(int version) {
+            return (posixPerms < 0) ? version :
+                VERSION_BASE_UNIX | (version & 0xff);
+        }
+
         ///////////////////// CEN //////////////////////
         static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
             throws IOException
         {
             return new Entry().cen(zipfs, inode);

@@ -1956,10 +1986,13 @@
             versionMade = CENVEM(cen, pos);
             disk        = CENDSK(cen, pos);
             attrs       = CENATT(cen, pos);
             attrsEx     = CENATX(cen, pos);
             */
+            if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
+                posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
+            }
             locoff      = CENOFF(cen, pos);
             pos += CENHDR;
             this.name = inode.name;
             this.isdir = inode.isdir;
             this.hashcode = inode.hashcode;

@@ -1974,14 +2007,11 @@
                 comment = Arrays.copyOfRange(cen, pos, pos + clen);
             }
             return this;
         }
 
-        int writeCEN(OutputStream os) throws IOException
-        {
-            int written  = CENHDR;
-            int version0 = version();
+        int writeCEN(OutputStream os) throws IOException {
             long csize0  = csize;
             long size0   = size;
             long locoff0 = locoff;
             int elen64   = 0;                // extra for ZIP64
             int elenNTFS = 0;                // extra for NTFS (a/c/mtime)

@@ -2008,10 +2038,12 @@
                 elen64 += 8;                 // offset(8)
             }
             if (elen64 != 0) {
                 elen64 += 4;                 // header and data sz 4 bytes
             }
+            boolean zip64 = (elen64 != 0);
+            int version0 = version(zip64);
             while (eoff + 4 < elen) {
                 int tag = SH(extra, eoff);
                 int sz = SH(extra, eoff + 2);
                 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
                     foundExtraTime = true;

@@ -2024,17 +2056,12 @@
                 } else {                     // Extended Timestamp otherwise
                     elenEXTT = 9;            // only mtime in cen
                 }
             }
             writeInt(os, CENSIG);            // CEN header signature
-            if (elen64 != 0) {
-                writeShort(os, 45);          // ver 4.5 for zip64
-                writeShort(os, 45);
-            } else {
-                writeShort(os, version0);    // version made by
-                writeShort(os, version0);    // version needed to extract
-            }
+            writeShort(os, versionMadeBy(version0)); // version made by
+            writeShort(os, version0);        // version needed to extract
             writeShort(os, flag);            // general purpose bit flag
             writeShort(os, method);          // compression method
                                              // last modification time
             writeInt(os, (int)javaToDosTime(mtime));
             writeInt(os, crc);               // crc-32

@@ -2048,14 +2075,16 @@
             } else {
                 writeShort(os, 0);
             }
             writeShort(os, 0);              // starting disk number
             writeShort(os, 0);              // internal file attributes (unused)
-            writeInt(os, 0);                // external file attributes (unused)
+            writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
+                                            // attributes, used for storing posix
+                                            // permissions
             writeInt(os, locoff0);          // relative offset of local header
             writeBytes(os, zname, 1, nlen);
-            if (elen64 != 0) {
+            if (zip64) {
                 writeShort(os, EXTID_ZIP64);// Zip64 extra
                 writeShort(os, elen64 - 4); // size of "this" extra block
                 if (size0 == ZIP64_MINVAL)
                     writeLong(os, size);
                 if (csize0 == ZIP64_MINVAL)

@@ -2091,22 +2120,21 @@
 
         ///////////////////// LOC //////////////////////
 
         int writeLOC(OutputStream os) throws IOException {
             writeInt(os, LOCSIG);               // LOC header signature
-            int version = version();
-
             byte[] zname = isdir ? toDirectoryPath(name) : name;
             int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
             int elen = (extra != null) ? extra.length : 0;
             boolean foundExtraTime = false;     // if extra timestamp present
             int eoff = 0;
             int elen64 = 0;
+            boolean zip64 = false;
             int elenEXTT = 0;
             int elenNTFS = 0;
             if ((flag & FLAG_DATADESCR) != 0) {
-                writeShort(os, version());      // version needed to extract
+                writeShort(os, version(zip64)); // version needed to extract
                 writeShort(os, flag);           // general purpose bit flag
                 writeShort(os, method);         // compression method
                 // last modification time
                 writeInt(os, (int)javaToDosTime(mtime));
                 // store size, uncompressed size, and crc-32 in data descriptor

@@ -2115,20 +2143,19 @@
                 writeInt(os, 0);
                 writeInt(os, 0);
             } else {
                 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
                     elen64 = 20;    //headid(2) + size(2) + size(8) + csize(8)
-                    writeShort(os, 45);         // ver 4.5 for zip64
-                } else {
-                    writeShort(os, version());  // version needed to extract
+                    zip64 = true;
                 }
+                writeShort(os, version(zip64)); // version needed to extract
                 writeShort(os, flag);           // general purpose bit flag
                 writeShort(os, method);         // compression method
                                                 // last modification time
                 writeInt(os, (int)javaToDosTime(mtime));
                 writeInt(os, crc);              // crc-32
-                if (elen64 != 0) {
+                if (zip64) {
                     writeInt(os, ZIP64_MINVAL);
                     writeInt(os, ZIP64_MINVAL);
                 } else {
                     writeInt(os, csize);        // compressed size
                     writeInt(os, size);         // uncompressed size

@@ -2154,11 +2181,11 @@
                 }
             }
             writeShort(os, nlen);
             writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
             writeBytes(os, zname, 1, nlen);
-            if (elen64 != 0) {
+            if (zip64) {
                 writeShort(os, EXTID_ZIP64);
                 writeShort(os, 16);
                 writeLong(os, size);
                 writeLong(os, csize);
             }

@@ -2407,23 +2434,59 @@
             fm.format("    fileKey         : %s%n", fileKey());
             fm.format("    size            : %d%n", size());
             fm.format("    compressedSize  : %d%n", compressedSize());
             fm.format("    crc             : %x%n", crc());
             fm.format("    method          : %d%n", method());
+            if (posixPerms != -1) {
+                fm.format("    permissions     : %s%n", permissions());
+            }
             fm.close();
             return sb.toString();
         }
+
+        @Override
+        public UserPrincipal owner() {
+            throw new UnsupportedOperationException(
+                "ZipFileSystem does not support owner.");
+        }
+
+        @Override
+        public GroupPrincipal group() {
+            throw new UnsupportedOperationException(
+                "ZipFileSystem does not support group.");
+        }
+
+        @Override
+        public Set<PosixFilePermission> permissions() {
+            if (posixPerms == -1) {
+                // in case there are no Posix permissions associated with the
+                // entry, we should not return an empty set of permissions
+                // because that would be an explicit set of permissions meaning
+                // no permissions for anyone
+                throw new UnsupportedOperationException(
+                    "No posix permissions associated with zip entry.");
+            }
+            return PosixFilePermissions.fromFlags(posixPerms);
+        }
+
+        @Override
+        public void setPermissions(Set<PosixFilePermission> perms) {
+            if (perms == null) {
+                posixPerms = -1;
+                return;
+            }
+            posixPerms = PosixFilePermissions.toFlags(perms);
+        }
     }
 
     // ZIP directory has two issues:
     // (1) ZIP spec does not require the ZIP file to include
     //     directory entry
     // (2) all entries are not stored/organized in a "tree"
     //     structure.
     // A possible solution is to build the node tree ourself as
     // implemented below.
-    private IndexNode root;
 
     // default time stamp for pseudo entries
     private long zfsDefaultTimeStamp = System.currentTimeMillis();
 
     private void removeFromTree(IndexNode inode) {
< prev index next >