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
024 package org.apache.activemq.console.command.store.tar;
025
026 import java.io.FilterInputStream;
027 import java.io.IOException;
028 import java.io.InputStream;
029 import java.io.OutputStream;
030
031 /**
032 * The TarInputStream reads a UNIX tar archive as an InputStream.
033 * methods are provided to position at each successive entry in
034 * the archive, and the read each entry as a normal input stream
035 * using read().
036 *
037 */
038 public class TarInputStream extends FilterInputStream {
039 private static final int SMALL_BUFFER_SIZE = 256;
040 private static final int BUFFER_SIZE = 8 * 1024;
041 private static final int LARGE_BUFFER_SIZE = 32 * 1024;
042 private static final int BYTE_MASK = 0xFF;
043
044 // CheckStyle:VisibilityModifier OFF - bc
045 protected boolean debug;
046 protected boolean hasHitEOF;
047 protected long entrySize;
048 protected long entryOffset;
049 protected byte[] readBuf;
050 protected TarBuffer buffer;
051 protected TarEntry currEntry;
052
053 /**
054 * This contents of this array is not used at all in this class,
055 * it is only here to avoid repreated object creation during calls
056 * to the no-arg read method.
057 */
058 protected byte[] oneBuf;
059
060 // CheckStyle:VisibilityModifier ON
061
062 /**
063 * Constructor for TarInputStream.
064 * @param is the input stream to use
065 */
066 public TarInputStream(InputStream is) {
067 this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
068 }
069
070 /**
071 * Constructor for TarInputStream.
072 * @param is the input stream to use
073 * @param blockSize the block size to use
074 */
075 public TarInputStream(InputStream is, int blockSize) {
076 this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
077 }
078
079 /**
080 * Constructor for TarInputStream.
081 * @param is the input stream to use
082 * @param blockSize the block size to use
083 * @param recordSize the record size to use
084 */
085 public TarInputStream(InputStream is, int blockSize, int recordSize) {
086 super(is);
087
088 this.buffer = new TarBuffer(is, blockSize, recordSize);
089 this.readBuf = null;
090 this.oneBuf = new byte[1];
091 this.debug = false;
092 this.hasHitEOF = false;
093 }
094
095 /**
096 * Sets the debugging flag.
097 *
098 * @param debug True to turn on debugging.
099 */
100 public void setDebug(boolean debug) {
101 this.debug = debug;
102 buffer.setDebug(debug);
103 }
104
105 /**
106 * Closes this stream. Calls the TarBuffer's close() method.
107 * @throws IOException on error
108 */
109 public void close() throws IOException {
110 buffer.close();
111 }
112
113 /**
114 * Get the record size being used by this stream's TarBuffer.
115 *
116 * @return The TarBuffer record size.
117 */
118 public int getRecordSize() {
119 return buffer.getRecordSize();
120 }
121
122 /**
123 * Get the available data that can be read from the current
124 * entry in the archive. This does not indicate how much data
125 * is left in the entire archive, only in the current entry.
126 * This value is determined from the entry's size header field
127 * and the amount of data already read from the current entry.
128 * Integer.MAX_VALUE is returen in case more than Integer.MAX_VALUE
129 * bytes are left in the current entry in the archive.
130 *
131 * @return The number of available bytes for the current entry.
132 * @throws IOException for signature
133 */
134 public int available() throws IOException {
135 if (entrySize - entryOffset > Integer.MAX_VALUE) {
136 return Integer.MAX_VALUE;
137 }
138 return (int) (entrySize - entryOffset);
139 }
140
141 /**
142 * Skip bytes in the input buffer. This skips bytes in the
143 * current entry's data, not the entire archive, and will
144 * stop at the end of the current entry's data if the number
145 * to skip extends beyond that point.
146 *
147 * @param numToSkip The number of bytes to skip.
148 * @return the number actually skipped
149 * @throws IOException on error
150 */
151 public long skip(long numToSkip) throws IOException {
152 // REVIEW
153 // This is horribly inefficient, but it ensures that we
154 // properly skip over bytes via the TarBuffer...
155 //
156 byte[] skipBuf = new byte[BUFFER_SIZE];
157 long skip = numToSkip;
158 while (skip > 0) {
159 int realSkip = (int) (skip > skipBuf.length ? skipBuf.length : skip);
160 int numRead = read(skipBuf, 0, realSkip);
161 if (numRead == -1) {
162 break;
163 }
164 skip -= numRead;
165 }
166 return (numToSkip - skip);
167 }
168
169 /**
170 * Since we do not support marking just yet, we return false.
171 *
172 * @return False.
173 */
174 public boolean markSupported() {
175 return false;
176 }
177
178 /**
179 * Since we do not support marking just yet, we do nothing.
180 *
181 * @param markLimit The limit to mark.
182 */
183 public void mark(int markLimit) {
184 }
185
186 /**
187 * Since we do not support marking just yet, we do nothing.
188 */
189 public void reset() {
190 }
191
192 /**
193 * Get the next entry in this tar archive. This will skip
194 * over any remaining data in the current entry, if there
195 * is one, and place the input stream at the header of the
196 * next entry, and read the header and instantiate a new
197 * TarEntry from the header bytes and return that entry.
198 * If there are no more entries in the archive, null will
199 * be returned to indicate that the end of the archive has
200 * been reached.
201 *
202 * @return The next TarEntry in the archive, or null.
203 * @throws IOException on error
204 */
205 public TarEntry getNextEntry() throws IOException {
206 if (hasHitEOF) {
207 return null;
208 }
209
210 if (currEntry != null) {
211 long numToSkip = entrySize - entryOffset;
212
213 if (debug) {
214 System.err.println("TarInputStream: SKIP currENTRY '"
215 + currEntry.getName() + "' SZ "
216 + entrySize + " OFF "
217 + entryOffset + " skipping "
218 + numToSkip + " bytes");
219 }
220
221 while (numToSkip > 0) {
222 long skipped = skip(numToSkip);
223 if (skipped <= 0) {
224 throw new RuntimeException("failed to skip current tar"
225 + " entry");
226 }
227 numToSkip -= skipped;
228 }
229
230 readBuf = null;
231 }
232
233 byte[] headerBuf = buffer.readRecord();
234
235 if (headerBuf == null) {
236 if (debug) {
237 System.err.println("READ NULL RECORD");
238 }
239 hasHitEOF = true;
240 } else if (buffer.isEOFRecord(headerBuf)) {
241 if (debug) {
242 System.err.println("READ EOF RECORD");
243 }
244 hasHitEOF = true;
245 }
246
247 if (hasHitEOF) {
248 currEntry = null;
249 } else {
250 currEntry = new TarEntry(headerBuf);
251
252 if (debug) {
253 System.err.println("TarInputStream: SET CURRENTRY '"
254 + currEntry.getName()
255 + "' size = "
256 + currEntry.getSize());
257 }
258
259 entryOffset = 0;
260
261 entrySize = currEntry.getSize();
262 }
263
264 if (currEntry != null && currEntry.isGNULongNameEntry()) {
265 // read in the name
266 StringBuffer longName = new StringBuffer();
267 byte[] buf = new byte[SMALL_BUFFER_SIZE];
268 int length = 0;
269 while ((length = read(buf)) >= 0) {
270 longName.append(new String(buf, 0, length));
271 }
272 getNextEntry();
273 if (currEntry == null) {
274 // Bugzilla: 40334
275 // Malformed tar file - long entry name not followed by entry
276 return null;
277 }
278 // remove trailing null terminator
279 if (longName.length() > 0
280 && longName.charAt(longName.length() - 1) == 0) {
281 longName.deleteCharAt(longName.length() - 1);
282 }
283 currEntry.setName(longName.toString());
284 }
285
286 return currEntry;
287 }
288
289 /**
290 * Reads a byte from the current tar archive entry.
291 *
292 * This method simply calls read( byte[], int, int ).
293 *
294 * @return The byte read, or -1 at EOF.
295 * @throws IOException on error
296 */
297 public int read() throws IOException {
298 int num = read(oneBuf, 0, 1);
299 return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;
300 }
301
302 /**
303 * Reads bytes from the current tar archive entry.
304 *
305 * This method is aware of the boundaries of the current
306 * entry in the archive and will deal with them as if they
307 * were this stream's start and EOF.
308 *
309 * @param buf The buffer into which to place bytes read.
310 * @param offset The offset at which to place bytes read.
311 * @param numToRead The number of bytes to read.
312 * @return The number of bytes read, or -1 at EOF.
313 * @throws IOException on error
314 */
315 public int read(byte[] buf, int offset, int numToRead) throws IOException {
316 int totalRead = 0;
317
318 if (entryOffset >= entrySize) {
319 return -1;
320 }
321
322 if ((numToRead + entryOffset) > entrySize) {
323 numToRead = (int) (entrySize - entryOffset);
324 }
325
326 if (readBuf != null) {
327 int sz = (numToRead > readBuf.length) ? readBuf.length
328 : numToRead;
329
330 System.arraycopy(readBuf, 0, buf, offset, sz);
331
332 if (sz >= readBuf.length) {
333 readBuf = null;
334 } else {
335 int newLen = readBuf.length - sz;
336 byte[] newBuf = new byte[newLen];
337
338 System.arraycopy(readBuf, sz, newBuf, 0, newLen);
339
340 readBuf = newBuf;
341 }
342
343 totalRead += sz;
344 numToRead -= sz;
345 offset += sz;
346 }
347
348 while (numToRead > 0) {
349 byte[] rec = buffer.readRecord();
350
351 if (rec == null) {
352 // Unexpected EOF!
353 throw new IOException("unexpected EOF with " + numToRead
354 + " bytes unread");
355 }
356
357 int sz = numToRead;
358 int recLen = rec.length;
359
360 if (recLen > sz) {
361 System.arraycopy(rec, 0, buf, offset, sz);
362
363 readBuf = new byte[recLen - sz];
364
365 System.arraycopy(rec, sz, readBuf, 0, recLen - sz);
366 } else {
367 sz = recLen;
368
369 System.arraycopy(rec, 0, buf, offset, recLen);
370 }
371
372 totalRead += sz;
373 numToRead -= sz;
374 offset += sz;
375 }
376
377 entryOffset += totalRead;
378
379 return totalRead;
380 }
381
382 /**
383 * Copies the contents of the current tar archive entry directly into
384 * an output stream.
385 *
386 * @param out The OutputStream into which to write the entry's data.
387 * @throws IOException on error
388 */
389 public void copyEntryContents(OutputStream out) throws IOException {
390 byte[] buf = new byte[LARGE_BUFFER_SIZE];
391
392 while (true) {
393 int numRead = read(buf, 0, buf.length);
394
395 if (numRead == -1) {
396 break;
397 }
398
399 out.write(buf, 0, numRead);
400 }
401 }
402 }