All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.io.archive.tar.TarInputArchive Maven / Gradle / Ivy

Go to download

TrueZIP is a Java based Virtual File System (VFS) to enable transparent, multi-threaded read/write access to archive files (ZIP, TAR etc.) as if they were directories. Archive files may be arbitrarily nested and the nesting level is only limited by heap and file system size.

The newest version!
/*
 * Copyright (C) 2006-2010 Schlichtherle IT Services
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.schlichtherle.io.archive.tar;

import de.schlichtherle.io.*;
import de.schlichtherle.io.File;
import de.schlichtherle.io.archive.spi.*;
import de.schlichtherle.io.util.*;

import java.io.*;
import java.util.*;
import java.util.zip.*;

import org.apache.tools.tar.*;

/**
 * Presents a {@link TarInputStream} as a randomly accessible archive.
 * 

* Warning: * The constructor of this class extracts each entry in the archive to a * temporary file. * This may be very time and space consuming for large archives, but is * the fastest implementation for subsequent random access, since there * is no way the archive driver could predict the client application's * behaviour. * * @author Christian Schlichtherle * @version $Id$ * @since TrueZIP 6.0 */ public class TarInputArchive implements InputArchive, TarConstants { private static final byte[] NULL_RECORD = new byte[TarBuffer.DEFAULT_RCDSIZE]; private static final int CHECKSUM_OFFSET = NAMELEN + MODELEN + UIDLEN + GIDLEN + SIZELEN + MODTIMELEN; /** Prefix for temporary files created by the multiplexer. */ private static final String TEMP_FILE_PREFIX = TarDriver.TEMP_FILE_PREFIX; /** * Maps entry names to tar entries [String -> TarEntry]. */ private final Map entries = new LinkedHashMap(); private InputArchiveMetaData metaData; /** * Extracts the entire TAR input stream into a temporary directory in order * to allow subsequent random access to its entries. * * @param in The input stream from which this input archive file should be * initialized. This stream is not used by any of the methods in * this class after the constructor has terminated and is * never closed! * So it is safe and recommended to close it upon termination * of this constructor. */ public TarInputArchive(final InputStream in) throws IOException { final TarInputStream tin = createValidatedTarInputStream(in); try { org.apache.tools.tar.TarEntry tinEntry; while ((tinEntry = tin.getNextEntry()) != null) { final String name = getName(tinEntry); TarEntry entry; if (tinEntry.isDirectory()) { entry = new TarEntry(tinEntry); } else { final java.io.File tmp; try { entry = (TarEntry) entries.get(name); tmp = entry != null ? entry.getFile() : Temps.createTempFile(TEMP_FILE_PREFIX); try { final java.io.FileOutputStream out = new java.io.FileOutputStream(tmp); try { File.cat(tin, out); // use high performance pump (async I/O) } finally { out.close(); } } catch (IOException ex) { final boolean ok = tmp.delete(); assert ok; throw ex; } } catch (InputIOException ex) { throw ex; } catch (IOException ex) { throw new TransientIOException( new TempFileException(tinEntry, ex)); } entry = new TarEntry(tinEntry, tmp); } entry.setName(name); // use normalized name entries.put(name, entry); } } catch (IOException failure) { close0(); throw failure; } } /** * Returns the fixed name of the given TAR entry, ensuring that it ends * with a {@link ArchiveEntry#SEPARATOR} if it's a directory. * * @param entry the TAR entry. * @return the fixed name of the given TAR entry. * @see Issue TRUEZIP-62 */ private static String getName(org.apache.tools.tar.TarEntry entry) { final String name = Paths.normalize(entry.getName(), ArchiveEntry.SEPARATOR_CHAR); return entry.isDirectory() && !name.endsWith(ArchiveEntry.SEPARATOR) ? name + ArchiveEntry.SEPARATOR_CHAR : name; } /** * Returns a newly created and validated {@link TarInputStream}. * This method performs a simple validation by computing the checksum * for the first record only. * This method is required because the {@code TarInputStream} * unfortunately does not do any validation! */ private static TarInputStream createValidatedTarInputStream( final InputStream in) throws IOException { final byte[] buf = new byte[TarBuffer.DEFAULT_RCDSIZE]; final InputStream vin = readAhead(in, buf); // If the record is the null record, the TAR file is empty and we're // done with validating. if (!Arrays.equals(buf, NULL_RECORD)) { final long expected = TarUtils.parseOctal(buf, CHECKSUM_OFFSET, 8); for (int i = 0; i < 8; i++) buf[CHECKSUM_OFFSET + i] = ' '; final long is = TarUtils.computeCheckSum(buf); if (expected != is) throw new IOException( "Illegal initial record in TAR file: Expected checksum " + expected + ", is " + is + "!"); } return new TarInputStream( vin, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE); } /** * Fills {@code buf} with data from the given input stream and * returns an input stream from which you can still read all data, * including the data in buf. * * @param in The stream to read from. May not be {@code null}. * @param buf The buffer to fill entirely with data. * @return A stream which holds all the data {@code in} did. * @throws IOException If {@code buf} couldn't get filled entirely. */ static InputStream readAhead(final InputStream in, final byte[] buf) throws IOException { // Unfortunately, in Sun's J2SE 1.4.2_12 implementation, // InflaterInputStream pretends to support marking, but actually it // doesn't - hence we need to filter this special case. // This issue has been fixed in Sun's J2SE 1.5.0-b64 implementation. if (!(in instanceof InflaterInputStream) && in.markSupported()) { in.mark(buf.length); readFully(in, buf); in.reset(); return in; } else { final PushbackInputStream pin = new PushbackInputStream(in, buf.length); readFully(pin, buf); pin.unread(buf); return pin; } } private static void readFully(final InputStream in, final byte[] buf) throws IOException { final int l = buf.length; int n = 0; do { final int r = in.read(buf, n, l - n); if (r == -1) throw new EOFException(); n += r; } while (n < l); } public int getNumArchiveEntries() { return entries.size(); } public Enumeration getArchiveEntries() { return Collections.enumeration(entries.values()); } public ArchiveEntry getArchiveEntry(String entryName) { return (TarEntry) entries.get(entryName); } public InputStream getInputStream( final ArchiveEntry entry, final ArchiveEntry dstEntry) throws IOException { return new java.io.FileInputStream(((TarEntry) entry).getFile()); } public void close() throws IOException { close0(); } private void close0() throws IOException { final Collection values = entries.values(); for (final Iterator i = values.iterator(); i.hasNext(); i.remove()) { final TarEntry entry = (TarEntry) i.next(); final java.io.File file = entry.getFile(); if (file == null) { assert entry.isDirectory(); continue; } assert file.exists(); if (!file.delete()) { // Windoze: The temp file is still open for reading by one // or more entry input streams. file.deleteOnExit(); } } } // // Metadata stuff. // public InputArchiveMetaData getMetaData() { return metaData; } public void setMetaData(InputArchiveMetaData metaData) { this.metaData = metaData; } // // Member class. // /** * This needs to be a {@link FileNotFoundException} in order to signal that * the TAR is simply not accessible and not necessarily a false positive. */ private static final class TempFileException extends FileNotFoundException { private TempFileException( final org.apache.tools.tar.TarEntry entry, final IOException cause) { super(entry.getName() + " (couldn't create temp file for archive entry)"); super.initCause(cause); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy