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

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

/*
 * 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.*;
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 EntryName#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 - 2025 Weber Informatics LLC | Privacy Policy