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 * 017 */ 018 019/* 020 * This package is based on the work done by Timothy Gerard Endres 021 * (time@ice.com) to whom the Ant project is very grateful for his great code. 022 */ 023 024package org.apache.activemq.console.command.store.tar; 025 026import java.io.FilterOutputStream; 027import java.io.OutputStream; 028import java.io.IOException; 029 030/** 031 * The TarOutputStream writes a UNIX tar archive as an OutputStream. 032 * Methods are provided to put entries, and then write their contents 033 * by writing to this stream using write(). 034 * 035 */ 036public class TarOutputStream extends FilterOutputStream { 037 /** Fail if a long file name is required in the archive. */ 038 public static final int LONGFILE_ERROR = 0; 039 040 /** Long paths will be truncated in the archive. */ 041 public static final int LONGFILE_TRUNCATE = 1; 042 043 /** GNU tar extensions are used to store long file names in the archive. */ 044 public static final int LONGFILE_GNU = 2; 045 046 // CheckStyle:VisibilityModifier OFF - bc 047 protected boolean debug; 048 protected long currSize; 049 protected String currName; 050 protected long currBytes; 051 protected byte[] oneBuf; 052 protected byte[] recordBuf; 053 protected int assemLen; 054 protected byte[] assemBuf; 055 protected TarBuffer buffer; 056 protected int longFileMode = LONGFILE_ERROR; 057 // CheckStyle:VisibilityModifier ON 058 059 private boolean closed = false; 060 061 /** 062 * Constructor for TarInputStream. 063 * @param os the output stream to use 064 */ 065 public TarOutputStream(OutputStream os) { 066 this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE); 067 } 068 069 /** 070 * Constructor for TarInputStream. 071 * @param os the output stream to use 072 * @param blockSize the block size to use 073 */ 074 public TarOutputStream(OutputStream os, int blockSize) { 075 this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE); 076 } 077 078 /** 079 * Constructor for TarInputStream. 080 * @param os the output stream to use 081 * @param blockSize the block size to use 082 * @param recordSize the record size to use 083 */ 084 public TarOutputStream(OutputStream os, int blockSize, int recordSize) { 085 super(os); 086 087 this.buffer = new TarBuffer(os, blockSize, recordSize); 088 this.debug = false; 089 this.assemLen = 0; 090 this.assemBuf = new byte[recordSize]; 091 this.recordBuf = new byte[recordSize]; 092 this.oneBuf = new byte[1]; 093 } 094 095 /** 096 * Set the long file mode. 097 * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2). 098 * This specifies the treatment of long file names (names >= TarConstants.NAMELEN). 099 * Default is LONGFILE_ERROR. 100 * @param longFileMode the mode to use 101 */ 102 public void setLongFileMode(int longFileMode) { 103 this.longFileMode = longFileMode; 104 } 105 106 107 /** 108 * Sets the debugging flag. 109 * 110 * @param debugF True to turn on debugging. 111 */ 112 public void setDebug(boolean debugF) { 113 this.debug = debugF; 114 } 115 116 /** 117 * Sets the debugging flag in this stream's TarBuffer. 118 * 119 * @param debug True to turn on debugging. 120 */ 121 public void setBufferDebug(boolean debug) { 122 buffer.setDebug(debug); 123 } 124 125 /** 126 * Ends the TAR archive without closing the underlying OutputStream. 127 * The result is that the two EOF records of nulls are written. 128 * @throws IOException on error 129 */ 130 public void finish() throws IOException { 131 // See Bugzilla 28776 for a discussion on this 132 // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776 133 writeEOFRecord(); 134 writeEOFRecord(); 135 buffer.flushBlock(); 136 } 137 138 /** 139 * Ends the TAR archive and closes the underlying OutputStream. 140 * This means that finish() is called followed by calling the 141 * TarBuffer's close(). 142 * @throws IOException on error 143 */ 144 public void close() throws IOException { 145 if (!closed) { 146 finish(); 147 buffer.close(); 148 out.close(); 149 closed = true; 150 } 151 } 152 153 /** 154 * Get the record size being used by this stream's TarBuffer. 155 * 156 * @return The TarBuffer record size. 157 */ 158 public int getRecordSize() { 159 return buffer.getRecordSize(); 160 } 161 162 /** 163 * Put an entry on the output stream. This writes the entry's 164 * header record and positions the output stream for writing 165 * the contents of the entry. Once this method is called, the 166 * stream is ready for calls to write() to write the entry's 167 * contents. Once the contents are written, closeEntry() 168 * <B>MUST</B> be called to ensure that all buffered data 169 * is completely written to the output stream. 170 * 171 * @param entry The TarEntry to be written to the archive. 172 * @throws IOException on error 173 */ 174 public void putNextEntry(TarEntry entry) throws IOException { 175 if (entry.getName().length() >= TarConstants.NAMELEN) { 176 177 if (longFileMode == LONGFILE_GNU) { 178 // create a TarEntry for the LongLink, the contents 179 // of which are the entry's name 180 TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK, 181 TarConstants.LF_GNUTYPE_LONGNAME); 182 183 longLinkEntry.setSize(entry.getName().length() + 1); 184 putNextEntry(longLinkEntry); 185 write(entry.getName().getBytes()); 186 write(0); 187 closeEntry(); 188 } else if (longFileMode != LONGFILE_TRUNCATE) { 189 throw new RuntimeException("file name '" + entry.getName() 190 + "' is too long ( > " 191 + TarConstants.NAMELEN + " bytes)"); 192 } 193 } 194 195 entry.writeEntryHeader(recordBuf); 196 buffer.writeRecord(recordBuf); 197 198 currBytes = 0; 199 200 if (entry.isDirectory()) { 201 currSize = 0; 202 } else { 203 currSize = entry.getSize(); 204 } 205 currName = entry.getName(); 206 } 207 208 /** 209 * Close an entry. This method MUST be called for all file 210 * entries that contain data. The reason is that we must 211 * buffer data written to the stream in order to satisfy 212 * the buffer's record based writes. Thus, there may be 213 * data fragments still being assembled that must be written 214 * to the output stream before this entry is closed and the 215 * next entry written. 216 * @throws IOException on error 217 */ 218 public void closeEntry() throws IOException { 219 if (assemLen > 0) { 220 for (int i = assemLen; i < assemBuf.length; ++i) { 221 assemBuf[i] = 0; 222 } 223 224 buffer.writeRecord(assemBuf); 225 226 currBytes += assemLen; 227 assemLen = 0; 228 } 229 230 if (currBytes < currSize) { 231 throw new IOException("entry '" + currName + "' closed at '" 232 + currBytes 233 + "' before the '" + currSize 234 + "' bytes specified in the header were written"); 235 } 236 } 237 238 /** 239 * Writes a byte to the current tar archive entry. 240 * 241 * This method simply calls read( byte[], int, int ). 242 * 243 * @param b The byte written. 244 * @throws IOException on error 245 */ 246 public void write(int b) throws IOException { 247 oneBuf[0] = (byte) b; 248 249 write(oneBuf, 0, 1); 250 } 251 252 /** 253 * Writes bytes to the current tar archive entry. 254 * 255 * This method simply calls write( byte[], int, int ). 256 * 257 * @param wBuf The buffer to write to the archive. 258 * @throws IOException on error 259 */ 260 public void write(byte[] wBuf) throws IOException { 261 write(wBuf, 0, wBuf.length); 262 } 263 264 /** 265 * Writes bytes to the current tar archive entry. This method 266 * is aware of the current entry and will throw an exception if 267 * you attempt to write bytes past the length specified for the 268 * current entry. The method is also (painfully) aware of the 269 * record buffering required by TarBuffer, and manages buffers 270 * that are not a multiple of recordsize in length, including 271 * assembling records from small buffers. 272 * 273 * @param wBuf The buffer to write to the archive. 274 * @param wOffset The offset in the buffer from which to get bytes. 275 * @param numToWrite The number of bytes to write. 276 * @throws IOException on error 277 */ 278 public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException { 279 if ((currBytes + numToWrite) > currSize) { 280 throw new IOException("request to write '" + numToWrite 281 + "' bytes exceeds size in header of '" 282 + currSize + "' bytes for entry '" 283 + currName + "'"); 284 285 // 286 // We have to deal with assembly!!! 287 // The programmer can be writing little 32 byte chunks for all 288 // we know, and we must assemble complete records for writing. 289 // REVIEW Maybe this should be in TarBuffer? Could that help to 290 // eliminate some of the buffer copying. 291 // 292 } 293 294 if (assemLen > 0) { 295 if ((assemLen + numToWrite) >= recordBuf.length) { 296 int aLen = recordBuf.length - assemLen; 297 298 System.arraycopy(assemBuf, 0, recordBuf, 0, 299 assemLen); 300 System.arraycopy(wBuf, wOffset, recordBuf, 301 assemLen, aLen); 302 buffer.writeRecord(recordBuf); 303 304 currBytes += recordBuf.length; 305 wOffset += aLen; 306 numToWrite -= aLen; 307 assemLen = 0; 308 } else { 309 System.arraycopy(wBuf, wOffset, assemBuf, assemLen, 310 numToWrite); 311 312 wOffset += numToWrite; 313 assemLen += numToWrite; 314 numToWrite = 0; 315 } 316 } 317 318 // 319 // When we get here we have EITHER: 320 // o An empty "assemble" buffer. 321 // o No bytes to write (numToWrite == 0) 322 // 323 while (numToWrite > 0) { 324 if (numToWrite < recordBuf.length) { 325 System.arraycopy(wBuf, wOffset, assemBuf, assemLen, 326 numToWrite); 327 328 assemLen += numToWrite; 329 330 break; 331 } 332 333 buffer.writeRecord(wBuf, wOffset); 334 335 int num = recordBuf.length; 336 337 currBytes += num; 338 numToWrite -= num; 339 wOffset += num; 340 } 341 } 342 343 /** 344 * Write an EOF (end of archive) record to the tar archive. 345 * An EOF record consists of a record of all zeros. 346 */ 347 private void writeEOFRecord() throws IOException { 348 for (int i = 0; i < recordBuf.length; ++i) { 349 recordBuf[i] = 0; 350 } 351 352 buffer.writeRecord(recordBuf); 353 } 354} 355 356