001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider.tar; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.nio.file.Files; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.Map; 026import java.util.Objects; 027import java.util.zip.GZIPInputStream; 028 029import org.apache.commons.compress.archivers.ArchiveEntry; 030import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 031import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.apache.commons.vfs2.Capability; 035import org.apache.commons.vfs2.FileName; 036import org.apache.commons.vfs2.FileNotFoundException; 037import org.apache.commons.vfs2.FileObject; 038import org.apache.commons.vfs2.FileSystemException; 039import org.apache.commons.vfs2.FileSystemOptions; 040import org.apache.commons.vfs2.Selectors; 041import org.apache.commons.vfs2.VfsLog; 042import org.apache.commons.vfs2.provider.AbstractFileName; 043import org.apache.commons.vfs2.provider.AbstractFileSystem; 044import org.apache.commons.vfs2.provider.UriParser; 045import org.apache.commons.vfs2.provider.bzip2.Bzip2FileObject; 046 047/** 048 * A read-only file system for Tar files. 049 */ 050public class TarFileSystem extends AbstractFileSystem { 051 private static final Log LOG = LogFactory.getLog(TarFileSystem.class); 052 053 private static final char[] ENC = {'!'}; 054 055 private final File file; 056 057 private TarArchiveInputStream tarFile; 058 059 /** 060 * Cache doesn't need to be synchronized since it is read-only. 061 */ 062 private final Map<FileName, FileObject> cache = new HashMap<>(); 063 064 /** 065 * Constructs a new instance. 066 * 067 * @param rootName The root file name of this file system. 068 * @param parentLayer The parent layer of this file system. 069 * @param fileSystemOptions Options to build this file system. 070 * @throws FileSystemException if a file system error occurs. 071 */ 072 protected TarFileSystem(final AbstractFileName rootName, final FileObject parentLayer, 073 final FileSystemOptions fileSystemOptions) throws FileSystemException { 074 super(rootName, parentLayer, fileSystemOptions); 075 076 // Make a local copy of the file 077 file = parentLayer.getFileSystem().replicateFile(parentLayer, Selectors.SELECT_SELF); 078 079 // Open the Tar file 080 if (!file.exists()) { 081 // Don't need to do anything 082 tarFile = null; 083 } 084 085 // tarFile = createTarFile(this.file); 086 } 087 088 /** 089 * Returns the capabilities of this file system. 090 */ 091 @Override 092 protected void addCapabilities(final Collection<Capability> caps) { 093 caps.addAll(TarFileProvider.capabilities); 094 } 095 096 /** 097 * Creates a file object. 098 */ 099 @Override 100 protected FileObject createFile(final AbstractFileName name) throws FileSystemException { 101 // This is only called for files which do not exist in the Tar file 102 return new TarFileObject(name, null, this, false); 103 } 104 105 /** 106 * Creates a new TarArchiveInputStream. 107 * 108 * @param file the file. 109 * @return a new TarArchiveInputStream. 110 * @throws FileSystemException if a file system error occurs. 111 */ 112 protected TarArchiveInputStream createTarFile(final File file) throws FileSystemException { 113 try { 114 if ("tgz".equalsIgnoreCase(getRootName().getScheme())) { 115 return new TarArchiveInputStream(new GZIPInputStream(Files.newInputStream(file.toPath()))); 116 } 117 if ("tbz2".equalsIgnoreCase(getRootName().getScheme())) { 118 return new TarArchiveInputStream( 119 Bzip2FileObject.wrapInputStream(file.getAbsolutePath(), Files.newInputStream(file.toPath()))); 120 } 121 return new TarArchiveInputStream(Files.newInputStream(file.toPath())); 122 } catch (final IOException ioe) { 123 throw new FileSystemException("vfs.provider.tar/open-tar-file.error", file, ioe); 124 } 125 } 126 127 /** 128 * Creates a new TarFileObject. 129 * 130 * @param fileName the file name. 131 * @param entry the archive entry. 132 * @return a new TarFileObject. 133 */ 134 protected TarFileObject createTarFileObject(final AbstractFileName fileName, final TarArchiveEntry entry) { 135 return new TarFileObject(fileName, entry, this, true); 136 } 137 138 @Override 139 protected void doCloseCommunicationLink() { 140 // Release the tar file 141 try { 142 if (tarFile != null) { 143 tarFile.close(); 144 tarFile = null; 145 } 146 } catch (final IOException e) { 147 // getLogger().warn("vfs.provider.tar/close-tar-file.error :" + file, e); 148 VfsLog.warn(getLogger(), LOG, "vfs.provider.tar/close-tar-file.error :" + file, e); 149 } 150 } 151 152 /** 153 * Returns a cached file. 154 */ 155 @Override 156 protected FileObject getFileFromCache(final FileName name) { 157 return cache.get(name); 158 } 159 160 /** 161 * Returns the input stream for the given entry. 162 * 163 * @param entry The entry to seek. 164 * @return the input stream for the given entry. 165 * @throws FileSystemException If an I/O error occurs. 166 */ 167 public InputStream getInputStream(final TarArchiveEntry entry) throws FileSystemException { 168 Objects.requireNonNull(entry, "entry"); 169 resetTarFile(); 170 try { 171 ArchiveEntry next; 172 while ((next = tarFile.getNextEntry()) != null) { 173 if (next.equals(entry)) { 174 return tarFile; 175 } 176 } 177 throw new FileNotFoundException(entry.toString()); 178 } catch (final IOException e) { 179 throw new FileSystemException(e); 180 } 181 } 182 183 /** 184 * Gets the TarArchiveInputStream. 185 * 186 * @return the TarArchiveInputStream. 187 * @throws FileSystemException if a file system error occurs. 188 */ 189 protected TarArchiveInputStream getTarFile() throws FileSystemException { 190 if (tarFile == null && file.exists()) { 191 recreateTarFile(); 192 } 193 return tarFile; 194 } 195 196 @Override 197 public void init() throws FileSystemException { 198 super.init(); 199 200 // Build the index 201 try { 202 TarArchiveEntry entry; 203 while ((entry = getTarFile().getNextTarEntry()) != null) { 204 final AbstractFileName name = (AbstractFileName) getFileSystemManager().resolveName(getRootName(), 205 UriParser.encode(entry.getName(), ENC)); 206 207 // Create the file 208 TarFileObject fileObj; 209 if (entry.isDirectory() && getFileFromCache(name) != null) { 210 fileObj = (TarFileObject) getFileFromCache(name); 211 fileObj.setTarEntry(entry); 212 continue; 213 } 214 215 fileObj = createTarFileObject(name, entry); 216 putFileToCache(fileObj); 217 218 // Make sure all ancestors exist 219 // TODO - create these on demand 220 TarFileObject parent = null; 221 for (AbstractFileName parentName = (AbstractFileName) name 222 .getParent(); parentName != null; fileObj = parent, parentName = (AbstractFileName) parentName 223 .getParent()) { 224 // Locate the parent 225 parent = (TarFileObject) getFileFromCache(parentName); 226 if (parent == null) { 227 parent = createTarFileObject(parentName, null); 228 putFileToCache(parent); 229 } 230 231 // Attach child to parent 232 parent.attachChild(fileObj.getName()); 233 } 234 } 235 } catch (final IOException e) { 236 throw new FileSystemException(e); 237 } finally { 238 closeCommunicationLink(); 239 } 240 } 241 242 /** 243 * Adds a file object to the cache. 244 */ 245 @Override 246 protected void putFileToCache(final FileObject file) { 247 cache.put(file.getName(), file); 248 } 249 250 /** 251 * will be called after all file-objects closed their streams. protected void notifyAllStreamsClosed() { 252 * closeCommunicationLink(); } 253 */ 254 private void recreateTarFile() throws FileSystemException { 255 if (tarFile != null) { 256 try { 257 tarFile.close(); 258 } catch (final IOException e) { 259 throw new FileSystemException("vfs.provider.tar/close-tar-file.error", file, e); 260 } 261 tarFile = null; 262 } 263 tarFile = createTarFile(file); 264 } 265 266 /** 267 * remove a cached file. 268 */ 269 @Override 270 protected void removeFileFromCache(final FileName name) { 271 cache.remove(name); 272 } 273 274 /** 275 * Resets the tar file. 276 * 277 * @throws FileSystemException if a file system error occurs. 278 */ 279 protected void resetTarFile() throws FileSystemException { 280 // Reading specific entries requires skipping through the tar file from the beginning 281 // Not especially elegant, but we don't have the ability to seek to specific positions 282 // with an input stream. 283 if (file.exists()) { 284 recreateTarFile(); 285 } 286 } 287}