You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yacy_search_server/source/org/apache/tools/tar/TarOutputStream.java

360 lines
12 KiB

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*
*/
/*
* This package is based on the work done by Timothy Gerard Endres
* (time@ice.com) to whom the Ant project is very grateful for his great code.
*/
package org.apache.tools.tar;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.IOException;
/**
* The TarOutputStream writes a UNIX tar archive as an OutputStream.
* Methods are provided to put entries, and then write their contents
* by writing to this stream using write().
*
*/
public class TarOutputStream extends FilterOutputStream {
/** Fail if a long file name is required in the archive. */
public static final int LONGFILE_ERROR = 0;
/** Long paths will be truncated in the archive. */
public static final int LONGFILE_TRUNCATE = 1;
/** GNU tar extensions are used to store long file names in the archive. */
public static final int LONGFILE_GNU = 2;
// CheckStyle:VisibilityModifier OFF - bc
protected boolean debug;
protected long currSize;
protected String currName;
protected long currBytes;
protected byte[] oneBuf;
protected byte[] recordBuf;
protected int assemLen;
protected byte[] assemBuf;
protected TarBuffer buffer;
protected int longFileMode = LONGFILE_ERROR;
// CheckStyle:VisibilityModifier ON
private boolean closed = false;
/**
* Constructor for TarInputStream.
* @param os the output stream to use
*/
public TarOutputStream(OutputStream os) {
this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
}
/**
* Constructor for TarInputStream.
* @param os the output stream to use
* @param blockSize the block size to use
*/
public TarOutputStream(OutputStream os, int blockSize) {
this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
}
/**
* Constructor for TarInputStream.
* @param os the output stream to use
* @param blockSize the block size to use
* @param recordSize the record size to use
*/
public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
super(os);
this.buffer = new TarBuffer(os, blockSize, recordSize);
this.debug = false;
this.assemLen = 0;
this.assemBuf = new byte[recordSize];
this.recordBuf = new byte[recordSize];
this.oneBuf = new byte[1];
}
/**
* Set the long file mode.
* This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).
* This specifies the treatment of long file names (names >= TarConstants.NAMELEN).
* Default is LONGFILE_ERROR.
* @param longFileMode the mode to use
*/
public void setLongFileMode(int longFileMode) {
this.longFileMode = longFileMode;
}
/**
* Sets the debugging flag.
*
* @param debugF True to turn on debugging.
*/
public void setDebug(boolean debugF) {
this.debug = debugF;
}
/**
* Sets the debugging flag in this stream's TarBuffer.
*
* @param debug True to turn on debugging.
*/
public void setBufferDebug(boolean debug) {
this.buffer.setDebug(debug);
}
/**
* Ends the TAR archive without closing the underlying OutputStream.
* The result is that the two EOF records of nulls are written.
* @throws IOException on error
*/
public void finish() throws IOException {
// See Bugzilla 28776 for a discussion on this
// http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
this.writeEOFRecord();
this.writeEOFRecord();
}
/**
* Ends the TAR archive and closes the underlying OutputStream.
* This means that finish() is called followed by calling the
* TarBuffer's close().
* @throws IOException on error
*/
@Override
public synchronized void close() throws IOException {
if (!closed) {
this.finish();
this.buffer.close();
out.close();
closed = true;
}
}
/**
* Get the record size being used by this stream's TarBuffer.
*
* @return The TarBuffer record size.
*/
public int getRecordSize() {
return this.buffer.getRecordSize();
}
/**
* Put an entry on the output stream. This writes the entry's
* header record and positions the output stream for writing
* the contents of the entry. Once this method is called, the
* stream is ready for calls to write() to write the entry's
* contents. Once the contents are written, closeEntry()
* <B>MUST</B> be called to ensure that all buffered data
* is completely written to the output stream.
*
* @param entry The TarEntry to be written to the archive.
* @throws IOException on error
*/
public void putNextEntry(TarEntry entry) throws IOException {
if (entry.getName().length() >= TarConstants.NAMELEN) {
if (longFileMode == LONGFILE_GNU) {
// create a TarEntry for the LongLink, the contents
// of which are the entry's name
TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
TarConstants.LF_GNUTYPE_LONGNAME);
longLinkEntry.setSize(entry.getName().length() + 1);
putNextEntry(longLinkEntry);
write(entry.getName().getBytes());
write(0);
closeEntry();
} else if (longFileMode != LONGFILE_TRUNCATE) {
throw new RuntimeException("file name '" + entry.getName()
+ "' is too long ( > "
+ TarConstants.NAMELEN + " bytes)");
}
}
entry.writeEntryHeader(this.recordBuf);
this.buffer.writeRecord(this.recordBuf);
this.currBytes = 0;
if (entry.isDirectory()) {
this.currSize = 0;
} else {
this.currSize = entry.getSize();
}
currName = entry.getName();
}
/**
* Close an entry. This method MUST be called for all file
* entries that contain data. The reason is that we must
* buffer data written to the stream in order to satisfy
* the buffer's record based writes. Thus, there may be
* data fragments still being assembled that must be written
* to the output stream before this entry is closed and the
* next entry written.
* @throws IOException on error
*/
public void closeEntry() throws IOException {
if (this.assemLen > 0) {
for (int i = this.assemLen; i < this.assemBuf.length; ++i) {
this.assemBuf[i] = 0;
}
this.buffer.writeRecord(this.assemBuf);
this.currBytes += this.assemLen;
this.assemLen = 0;
}
if (this.currBytes < this.currSize) {
throw new IOException("entry '" + currName + "' closed at '"
+ this.currBytes
+ "' before the '" + this.currSize
+ "' bytes specified in the header were written");
}
}
/**
* Writes a byte to the current tar archive entry.
*
* This method simply calls read( byte[], int, int ).
*
* @param b The byte written.
* @throws IOException on error
*/
@Override
public void write(int b) throws IOException {
this.oneBuf[0] = (byte) b;
this.write(this.oneBuf, 0, 1);
}
/**
* Writes bytes to the current tar archive entry.
*
* This method simply calls write( byte[], int, int ).
*
* @param wBuf The buffer to write to the archive.
* @throws IOException on error
*/
@Override
public void write(byte[] wBuf) throws IOException {
this.write(wBuf, 0, wBuf.length);
}
/**
* Writes bytes to the current tar archive entry. This method
* is aware of the current entry and will throw an exception if
* you attempt to write bytes past the length specified for the
* current entry. The method is also (painfully) aware of the
* record buffering required by TarBuffer, and manages buffers
* that are not a multiple of recordsize in length, including
* assembling records from small buffers.
*
* @param wBuf The buffer to write to the archive.
* @param wOffset The offset in the buffer from which to get bytes.
* @param numToWrite The number of bytes to write.
* @throws IOException on error
*/
@Override
public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
if ((this.currBytes + numToWrite) > this.currSize) {
throw new IOException("request to write '" + numToWrite
+ "' bytes exceeds size in header of '"
+ this.currSize + "' bytes for entry '"
+ currName + "'");
//
// We have to deal with assembly!!!
// The programmer can be writing little 32 byte chunks for all
// we know, and we must assemble complete records for writing.
// REVIEW Maybe this should be in TarBuffer? Could that help to
// eliminate some of the buffer copying.
//
}
if (this.assemLen > 0) {
if ((this.assemLen + numToWrite) >= this.recordBuf.length) {
int aLen = this.recordBuf.length - this.assemLen;
System.arraycopy(this.assemBuf, 0, this.recordBuf, 0,
this.assemLen);
System.arraycopy(wBuf, wOffset, this.recordBuf,
this.assemLen, aLen);
this.buffer.writeRecord(this.recordBuf);
this.currBytes += this.recordBuf.length;
wOffset += aLen;
numToWrite -= aLen;
this.assemLen = 0;
} else {
System.arraycopy(wBuf, wOffset, this.assemBuf, this.assemLen,
numToWrite);
wOffset += numToWrite;
this.assemLen += numToWrite;
numToWrite -= numToWrite;
}
}
//
// When we get here we have EITHER:
// o An empty "assemble" buffer.
// o No bytes to write (numToWrite == 0)
//
while (numToWrite > 0) {
if (numToWrite < this.recordBuf.length) {
System.arraycopy(wBuf, wOffset, this.assemBuf, this.assemLen,
numToWrite);
this.assemLen += numToWrite;
break;
}
this.buffer.writeRecord(wBuf, wOffset);
int num = this.recordBuf.length;
this.currBytes += num;
numToWrite -= num;
wOffset += num;
}
}
/**
* Write an EOF (end of archive) record to the tar archive.
* An EOF record consists of a record of all zeros.
*/
private void writeEOFRecord() throws IOException {
for (int i = 0; i < this.recordBuf.length; ++i) {
this.recordBuf[i] = 0;
}
this.buffer.writeRecord(this.recordBuf);
}
}