Поставщик файловой системы Zip Java: ReadOnly на удаленном диске [Windows]

У меня проблема с провайдером файловой системы Zip: если zip-файл находится на удаленном диске (отображается или нет, кажется, неактуальным), виртуальная файловая система доступна только для чтения, хотя сам файл - нет. Я написал минимальный пример кода:

public static void main(String[] args) throws IOException {
    File workingDir = new File(args[0]);
    File source = new File(workingDir, "in.zip");
    File target = new File(workingDir, "out.zip");
    Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);

    try (FileSystem zipfs = FileSystems.newFileSystem(target.toPath(), null)) {
        Path pathInZipfile = zipfs.getPath("test.xml");
        System.out.println("zipfile writable: " + target.canWrite());
        System.out.println("zipFS writable: " + !zipfs.isReadOnly());
        Files.delete(pathInZipfile);
        System.out.println("File successfully deleted");   
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Если workingDir - локальный каталог, все работает нормально. Однако, если это (сопоставленный) удаленный диск, я получаю:

zipfile writable: true
zipFS writable: false
Exception in thread "main" java.nio.file.ReadOnlyFileSystemException
    at com.sun.nio.zipfs.ZipFileSystem.checkWritable(ZipFileSystem.java:155)
    at com.sun.nio.zipfs.ZipFileSystem.deleteFile(ZipFileSystem.java:1335)
    at com.sun.nio.zipfs.ZipPath.delete(ZipPath.java:655)
    at com.sun.nio.zipfs.ZipFileSystemProvider.delete(ZipFileSystemProvider.java:206)
    at java.nio.file.Files.delete(Unknown Source)
    at zipfs.ZipFS.main(ZipFS.java:23)

Я делаю что-то неправильно? Это невозможно? Есть ли обходной путь?

3 ответа

Решение

Я столкнулся с тем же и посмотрел код JDK.

Выводы

В ZipFileSystem.java есть три соответствующие строки:

zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
if (!Files.isWritable(zfpath))
    this.readOnly = true;

zfPath - это объект Path. Что-то в поставщике файловой системы Windows блокирует доступ на запись к пути zip-архива. Не похоже, что с этим многое можно сделать.

Временное решение

То, что я использовал в качестве обходного пути, было:

  1. Создайте zip-архив во временной папке
  2. Заполните zip-архив
  3. Скопируйте временный файл в исходное расположение подключенного диска

Пока подключенный диск доступен для записи в контекстах вне zip-файловой системы, этот метод работает.

Мы столкнулись с точно такой же проблемой и придумали несколько варварское решение. FileSystems.newFileSystem(Path)принимает интерфейс, поэтому его можно удовлетворить, обернув реальный Pathпример.

Вот решение, которое реализует требуемый FileSysteProvider.checkAccess():

        private static FileSystem createZip(Path zipPath)
    throws Exception
  {
    var fileSystem = zipPath.getFileSystem();
    var provider = fileSystem.provider();
    
    return FileSystems.newFileSystem(
      new Path()
      {
        private Path path(Path path)
        {
          return this == path ? zipPath : path;
        }
        
        @Override
        public FileSystem getFileSystem()
        {
          return new FileSystem()
          {
            public Set<String> supportedFileAttributeViews()
            {
              return fileSystem.supportedFileAttributeViews();
            }
            
            @Override
            public FileSystemProvider provider()
            {
              return new FileSystemProvider()
              {
                @Override
                public void setAttribute(
                  Path path, 
                  String attribute, 
                  Object value,
                  LinkOption... options) 
                  throws IOException
                {
                  provider.setAttribute(path(path), attribute, value, options);
                }
                
                @Override
                public Map<String, Object> readAttributes(
                  Path path, 
                  String attributes,
                  LinkOption... options) 
                  throws IOException
                {
                  return provider.
                    readAttributes(path(path), attributes, options);
                }
                
                @Override
                public <A extends BasicFileAttributes> A readAttributes(
                  Path path,
                  Class<A> type, 
                  LinkOption... options) 
                  throws IOException
                {
                  return provider.readAttributes(path(path), type, options);
                }
                
                @Override
                public FileSystem newFileSystem(URI uri, Map<String, ?> env)
                  throws IOException
                {
                  return provider.newFileSystem(uri, env);
                }
                
                @Override
                public DirectoryStream<Path> newDirectoryStream(
                  Path dir,
                  Filter<? super Path> filter) 
                  throws IOException
                {
                  return provider.newDirectoryStream(dir, filter);
                }
                
                @Override
                public SeekableByteChannel newByteChannel(
                  Path path,
                  Set<? extends OpenOption> options, 
                  FileAttribute<?>... attrs)
                  throws IOException
                {
                  return provider.newByteChannel(path(path), options, attrs);
                }
                
                @Override
                public void move(
                  Path source, 
                  Path target, 
                  CopyOption... options)
                  throws IOException
                {
                  provider.move(path(source), path(target), options);
                }
                
                @Override
                public boolean isSameFile(Path path, Path path2) 
                  throws IOException
                {
                  return provider.isSameFile(path(path), path(path2));
                }
                
                @Override
                public boolean isHidden(Path path) 
                  throws IOException
                {
                  return provider.isHidden(path(path));
                }
                
                @Override
                public String getScheme()
                {
                  return provider.getScheme();
                }
                
                @Override
                public Path getPath(URI uri)
                {
                  return provider.getPath(uri);
                }
                
                @Override
                public FileSystem getFileSystem(URI uri)
                {
                  return provider.getFileSystem(uri);
                }
                
                @Override
                public FileStore getFileStore(Path path) 
                  throws IOException
                {
                  return provider.getFileStore(path(path));
                }
                
                @Override
                public <V extends FileAttributeView> V getFileAttributeView(
                  Path path,
                  Class<V> type, 
                  LinkOption... options)
                {
                  return provider.
                    getFileAttributeView(path(path), type, options);
                }
                
                @Override
                public void delete(Path path) 
                  throws IOException
                {
                  provider.delete(path(path));
                }
                
                @Override
                public void createDirectory(Path dir, FileAttribute<?>... attrs)
                    throws IOException
                {
                  provider.createDirectory(path(dir), attrs);
                }
                
                @Override
                public void copy(
                  Path source, 
                  Path target, 
                  CopyOption... options)
                  throws IOException
                {
                  provider.copy(path(source), path(target), options);                      
                }
                
                @Override
                public void checkAccess(Path path, AccessMode... modes)
                  throws IOException
                {
                  if ((modes != null) && 
                    (modes.length == 1) && 
                    (modes[0] == AccessMode.WRITE))
                  {
                    return;
                  }
                  
                  provider.checkAccess(path(path), modes);
                }
              };
            }
            
            @Override
            public WatchService newWatchService()
              throws IOException
            {
              return fileSystem.newWatchService();
            }
            
            @Override
            public boolean isReadOnly()
            {
              return false;
            }
            
            @Override
            public boolean isOpen()
            {
              return fileSystem.isOpen();
            }
            
            @Override
            public UserPrincipalLookupService getUserPrincipalLookupService()
            {
              return fileSystem.getUserPrincipalLookupService();
            }
            
            @Override
            public String getSeparator()
            {
              return fileSystem.getSeparator();
            }
            
            @Override
            public Iterable<Path> getRootDirectories()
            {
              return fileSystem.getRootDirectories();
            }
            
            @Override
            public PathMatcher getPathMatcher(String syntaxAndPattern)
            {
              return fileSystem.getPathMatcher(syntaxAndPattern);
            }
            
            @Override
            public Path getPath(String first, String... more)
            {
              return fileSystem.getPath(first, more);
            }
            
            @Override
            public Iterable<FileStore> getFileStores()
            {
              return fileSystem.getFileStores();
            }
            
            @Override
            public void close() throws IOException
            {
              fileSystem.close();                  
            }
          };
        }
  
        @Override
        public boolean isAbsolute()
        {
          return zipPath.isAbsolute();
        }
  
        @Override
        public Path getRoot()
        {
          return zipPath.getRoot();
        }
  
        @Override
        public Path getFileName()
        {
          return zipPath.getFileName();
        }
  
        @Override
        public Path getParent()
        {
          return zipPath.getParent();
        }
  
        @Override
        public int getNameCount()
        {
          return zipPath.getNameCount();
        }
  
        @Override
        public Path getName(int index)
        {
          return zipPath.getName(index);
        }
  
        @Override
        public Path subpath(int beginIndex, int endIndex)
        {
          return zipPath.subpath(beginIndex, endIndex);
        }
  
        @Override
        public boolean startsWith(Path other)
        {
          return zipPath.startsWith(other);
        }
  
        @Override
        public boolean endsWith(Path other)
        {
          return zipPath.endsWith(other);
        }
  
        @Override
        public Path normalize()
        {
          return zipPath.normalize();
        }
  
        @Override
        public Path resolve(Path other)
        {
          return zipPath.relativize(other);
        }
  
        @Override
        public Path relativize(Path other)
        {
          return zipPath.relativize(other);
        }
  
        @Override
        public URI toUri()
        {
          return zipPath.toUri();
        }
  
        @Override
        public Path toAbsolutePath()
        {
          return zipPath.toAbsolutePath();
        }
  
        @Override
        public Path toRealPath(LinkOption... options) 
          throws IOException
        {
          return zipPath.toRealPath(options);
        }
  
        @Override
        public WatchKey register(
          WatchService watcher, 
          Kind<?>[] events,
          Modifier... modifiers) 
          throws IOException
        {
          return zipPath.register(watcher, events, modifiers);
        }
  
        @Override
        public int compareTo(Path other)
        {
          return zipPath.compareTo(other);
        }
      }, 
      Map.of("create", "true"));
  }

Это больше похоже на взлом, но мы думаем, что он решает ошибку в оригинале. ZipFileSystem

Попробуйте установить приватное поле readOnly в ZipFileSystem в false с отражением.

Другие вопросы по тегам