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

de.schlichtherle.truezip.fs.archive.tar.TarInputShop Maven / Gradle / Ivy

Go to download

The file system driver family for TAR and related archive file types. Add the JAR artifact of this module to the run time class path to make its file system drivers available for service location in the client API modules.

The newest version!
/*
 * Copyright (C) 2005-2015 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package de.schlichtherle.truezip.fs.archive.tar;

import de.schlichtherle.truezip.entry.Entry.Type;
import static de.schlichtherle.truezip.entry.Entry.Type.DIRECTORY;
import static de.schlichtherle.truezip.entry.Entry.Type.FILE;
import de.schlichtherle.truezip.fs.FsCharsetArchiveDriver;
import de.schlichtherle.truezip.io.SequentialIOException;
import de.schlichtherle.truezip.io.SequentialIOExceptionBuilder;
import de.schlichtherle.truezip.io.Streams;
import de.schlichtherle.truezip.rof.ReadOnlyFile;
import de.schlichtherle.truezip.socket.IOEntry;
import de.schlichtherle.truezip.socket.IOPool;
import de.schlichtherle.truezip.socket.IOPool.Entry;
import de.schlichtherle.truezip.socket.InputShop;
import de.schlichtherle.truezip.socket.InputSocket;
import static de.schlichtherle.truezip.util.HashMaps.OVERHEAD_SIZE;
import static de.schlichtherle.truezip.util.HashMaps.initialCapacity;
import de.schlichtherle.truezip.util.JSE7;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.nio.channels.SeekableByteChannel;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.NotThreadSafe;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import static org.apache.commons.compress.archivers.tar.TarConstants.DEFAULT_BLKSIZE;
import static org.apache.commons.compress.archivers.tar.TarConstants.DEFAULT_RCDSIZE;
import static org.apache.commons.compress.archivers.tar.TarConstants.GIDLEN;
import static org.apache.commons.compress.archivers.tar.TarConstants.MODELEN;
import static org.apache.commons.compress.archivers.tar.TarConstants.MODTIMELEN;
import static org.apache.commons.compress.archivers.tar.TarConstants.NAMELEN;
import static org.apache.commons.compress.archivers.tar.TarConstants.SIZELEN;
import static org.apache.commons.compress.archivers.tar.TarConstants.UIDLEN;
import org.apache.commons.compress.archivers.tar.TarUtils;

/**
 * An input shop for reading TAR files.
 * 

* Note that 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. * * @see TarOutputShop * @author Christian Schlichtherle */ @NotThreadSafe public class TarInputShop implements InputShop { private static final byte[] NULL_RECORD = new byte[DEFAULT_RCDSIZE]; private static final int CHECKSUM_OFFSET = NAMELEN + MODELEN + UIDLEN + GIDLEN + SIZELEN + MODTIMELEN; /** HashMaps entry names to I/O pool entries. */ private final Map entries = new LinkedHashMap( initialCapacity(OVERHEAD_SIZE)); private final TarDriver driver; @CreatesObligation public TarInputShop(final TarDriver driver, final @WillNotClose InputStream in) throws EOFException, IOException { if (null == (this.driver = driver)) throw new NullPointerException(); try { unpack(newValidatedTarArchiveInputStream(in)); } catch (final IOException ex) { try { close0(); } catch (final Throwable ex2) { if (JSE7.AVAILABLE) ex.addSuppressed(ex2); } throw ex; } } private void unpack(final @WillNotClose TarArchiveInputStream tain) throws IOException { final TarDriver driver = this.driver; final IOPool pool = driver.getPool(); for ( TarArchiveEntry tinEntry; null != (tinEntry = tain.getNextTarEntry()); ) { final String name = name(tinEntry); TarDriverEntry entry = entries.get(name); if (null != entry) entry.release(); entry = driver.newEntry(name, tinEntry); if (!tinEntry.isDirectory()) { final Entry buffer = pool.allocate(); entry.setTemp(buffer); try { final OutputStream out = buffer.getOutputSocket().newOutputStream(); IOException ex = null; try { Streams.cat(tain, out); } catch (final IOException ex2) { ex = ex2; throw ex2; } finally { try { out.close(); } catch (final IOException ex2) { if (null == ex) throw ex2; if (JSE7.AVAILABLE) ex.addSuppressed(ex2); } } } catch (final IOException ex) { try { buffer.release(); } catch (final IOException ex2) { if (JSE7.AVAILABLE) ex.addSuppressed(ex2); } throw ex; } } entries.put(name, entry); } } private static String name(final TarArchiveEntry entry) { final String name = entry.getName(); final Type type = entry.isDirectory() ? DIRECTORY : FILE; return FsCharsetArchiveDriver.toZipOrTarEntryName(name, type); } /** * Returns a newly created and validated {@link TarArchiveInputStream}. * This method performs a simple validation by computing the checksum * for the first record only. * This method is required because the {@code TarArchiveInputStream} * unfortunately does not do any validation! * * @param in the stream to read from. * @return A stream which holds all the data {@code in} did. * @throws EOFException on unexpected end-of-file. * @throws IOException on any I/O error. */ private TarArchiveInputStream newValidatedTarArchiveInputStream(final InputStream in) throws EOFException, IOException { final byte[] buf = new byte[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; try { expected = TarUtils.parseOctal(buf, CHECKSUM_OFFSET, 8); } catch (IllegalArgumentException ex) { throw new IOException("Invalid initial record in TAR file!", ex); } for (int i = 0; i < 8; i++) buf[CHECKSUM_OFFSET + i] = ' '; final long actual = TarUtils.computeCheckSum(buf); if (expected != actual) throw new IOException( "Invalid initial record in TAR file: Expected / actual checksum : " + expected + " / " + actual + "!"); } return new TarArchiveInputStream( vin, DEFAULT_BLKSIZE, DEFAULT_RCDSIZE, driver.getEncoding()); } /** * 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 EOFException on unexpected end-of-file. * @throws IOException on any I/O error. */ static InputStream readAhead(final InputStream in, final byte[] buf) throws EOFException, IOException { if (in.markSupported()) { in.mark(buf.length); new DataInputStream(in).readFully(buf); in.reset(); return in; } else { final PushbackInputStream pin = new PushbackInputStream(in, buf.length); new DataInputStream(pin).readFully(buf); pin.unread(buf); return pin; } } @Override public final int getSize() { return entries.size(); } @Override public final Iterator iterator() { return Collections.unmodifiableCollection(entries.values()).iterator(); } @Override public final @CheckForNull TarDriverEntry getEntry(String name) { return entries.get(name); } @Override public InputSocket getInputSocket(final String name) { if (null == name) throw new NullPointerException(); final class Input extends InputSocket { @Override public TarDriverEntry getLocalTarget() throws IOException { final TarDriverEntry entry = getEntry(name); if (null == entry) throw new FileNotFoundException(name + " (entry not found)"); if (entry.isDirectory()) throw new FileNotFoundException(name + " (cannot read directory entries)"); return entry; } @Override public ReadOnlyFile newReadOnlyFile() throws IOException { return getInputSocket().newReadOnlyFile(); } @Override public SeekableByteChannel newSeekableByteChannel() throws IOException { return getInputSocket().newSeekableByteChannel(); } @Override public InputStream newInputStream() throws IOException { return getInputSocket().newInputStream(); } InputSocket> getInputSocket() throws IOException { return getLocalTarget().getTemp().getInputSocket(); } } // Input return new Input(); } @Override public void close() throws IOException { close0(); } private void close0() throws IOException { SequentialIOExceptionBuilder builder = SequentialIOExceptionBuilder.create(IOException.class); for (final Iterator i = entries.values().iterator(); i.hasNext(); i.remove()) { try { i.next().release(); } catch (final IOException ex) { builder.warn(ex); } } builder.check(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy