1 | /*
|
---|
2 | * Copyright 2000-2002,2004-2005 The Apache Software Foundation
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
---|
5 | * you may not use this file except in compliance with the License.
|
---|
6 | * You may obtain a copy of the License at
|
---|
7 | *
|
---|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
---|
9 | *
|
---|
10 | * Unless required by applicable law or agreed to in writing, software
|
---|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
---|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
---|
13 | * See the License for the specific language governing permissions and
|
---|
14 | * limitations under the License.
|
---|
15 | *
|
---|
16 | */
|
---|
17 |
|
---|
18 | /*
|
---|
19 | * This package is based on the work done by Timothy Gerard Endres
|
---|
20 | * ([email protected]) to whom the Ant project is very grateful for his great code.
|
---|
21 | */
|
---|
22 |
|
---|
23 | package org.apache.tools.tar;
|
---|
24 |
|
---|
25 | import java.io.FilterOutputStream;
|
---|
26 | import java.io.OutputStream;
|
---|
27 | import java.io.IOException;
|
---|
28 |
|
---|
29 | /**
|
---|
30 | * The TarOutputStream writes a UNIX tar archive as an OutputStream.
|
---|
31 | * Methods are provided to put entries, and then write their contents
|
---|
32 | * by writing to this stream using write().
|
---|
33 | *
|
---|
34 | */
|
---|
35 | public class TarOutputStream extends FilterOutputStream {
|
---|
36 | /** Fail if a long file name is required in the archive. */
|
---|
37 | public static final int LONGFILE_ERROR = 0;
|
---|
38 |
|
---|
39 | /** Long paths will be truncated in the archive. */
|
---|
40 | public static final int LONGFILE_TRUNCATE = 1;
|
---|
41 |
|
---|
42 | /** GNU tar extensions are used to store long file names in the archive. */
|
---|
43 | public static final int LONGFILE_GNU = 2;
|
---|
44 |
|
---|
45 | protected boolean debug;
|
---|
46 | protected int currSize;
|
---|
47 | protected int currBytes;
|
---|
48 | protected byte[] oneBuf;
|
---|
49 | protected byte[] recordBuf;
|
---|
50 | protected int assemLen;
|
---|
51 | protected byte[] assemBuf;
|
---|
52 | protected TarBuffer buffer;
|
---|
53 | protected int longFileMode = LONGFILE_ERROR;
|
---|
54 |
|
---|
55 | public TarOutputStream(OutputStream os) {
|
---|
56 | this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
|
---|
57 | }
|
---|
58 |
|
---|
59 | public TarOutputStream(OutputStream os, int blockSize) {
|
---|
60 | this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
|
---|
61 | }
|
---|
62 |
|
---|
63 | public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
|
---|
64 | super(os);
|
---|
65 |
|
---|
66 | this.buffer = new TarBuffer(os, blockSize, recordSize);
|
---|
67 | this.debug = false;
|
---|
68 | this.assemLen = 0;
|
---|
69 | this.assemBuf = new byte[recordSize];
|
---|
70 | this.recordBuf = new byte[recordSize];
|
---|
71 | this.oneBuf = new byte[1];
|
---|
72 | }
|
---|
73 |
|
---|
74 | public void setLongFileMode(int longFileMode) {
|
---|
75 | this.longFileMode = longFileMode;
|
---|
76 | }
|
---|
77 |
|
---|
78 |
|
---|
79 | /**
|
---|
80 | * Sets the debugging flag.
|
---|
81 | *
|
---|
82 | * @param debugF True to turn on debugging.
|
---|
83 | */
|
---|
84 | public void setDebug(boolean debugF) {
|
---|
85 | this.debug = debugF;
|
---|
86 | }
|
---|
87 |
|
---|
88 | /**
|
---|
89 | * Sets the debugging flag in this stream's TarBuffer.
|
---|
90 | *
|
---|
91 | * @param debug True to turn on debugging.
|
---|
92 | */
|
---|
93 | public void setBufferDebug(boolean debug) {
|
---|
94 | this.buffer.setDebug(debug);
|
---|
95 | }
|
---|
96 |
|
---|
97 | /**
|
---|
98 | * Ends the TAR archive without closing the underlying OutputStream.
|
---|
99 | * The result is that the two EOF records of nulls are written.
|
---|
100 | */
|
---|
101 | public void finish() throws IOException {
|
---|
102 | // See Bugzilla 28776 for a discussion on this
|
---|
103 | // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
|
---|
104 | this.writeEOFRecord();
|
---|
105 | this.writeEOFRecord();
|
---|
106 | }
|
---|
107 |
|
---|
108 | /**
|
---|
109 | * Ends the TAR archive and closes the underlying OutputStream.
|
---|
110 | * This means that finish() is called followed by calling the
|
---|
111 | * TarBuffer's close().
|
---|
112 | */
|
---|
113 | public void close() throws IOException {
|
---|
114 | this.finish();
|
---|
115 | this.buffer.close();
|
---|
116 | }
|
---|
117 |
|
---|
118 | /**
|
---|
119 | * Get the record size being used by this stream's TarBuffer.
|
---|
120 | *
|
---|
121 | * @return The TarBuffer record size.
|
---|
122 | */
|
---|
123 | public int getRecordSize() {
|
---|
124 | return this.buffer.getRecordSize();
|
---|
125 | }
|
---|
126 |
|
---|
127 | /**
|
---|
128 | * Put an entry on the output stream. This writes the entry's
|
---|
129 | * header record and positions the output stream for writing
|
---|
130 | * the contents of the entry. Once this method is called, the
|
---|
131 | * stream is ready for calls to write() to write the entry's
|
---|
132 | * contents. Once the contents are written, closeEntry()
|
---|
133 | * <B>MUST</B> be called to ensure that all buffered data
|
---|
134 | * is completely written to the output stream.
|
---|
135 | *
|
---|
136 | * @param entry The TarEntry to be written to the archive.
|
---|
137 | */
|
---|
138 | public void putNextEntry(TarEntry entry) throws IOException {
|
---|
139 | if (entry.getName().length() >= TarConstants.NAMELEN) {
|
---|
140 |
|
---|
141 | if (longFileMode == LONGFILE_GNU) {
|
---|
142 | // create a TarEntry for the LongLink, the contents
|
---|
143 | // of which are the entry's name
|
---|
144 | TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
|
---|
145 | TarConstants.LF_GNUTYPE_LONGNAME);
|
---|
146 |
|
---|
147 | longLinkEntry.setSize(entry.getName().length() + 1);
|
---|
148 | putNextEntry(longLinkEntry);
|
---|
149 | write(entry.getName().getBytes());
|
---|
150 | write(0);
|
---|
151 | closeEntry();
|
---|
152 | } else if (longFileMode != LONGFILE_TRUNCATE) {
|
---|
153 | throw new RuntimeException("file name '" + entry.getName()
|
---|
154 | + "' is too long ( > "
|
---|
155 | + TarConstants.NAMELEN + " bytes)");
|
---|
156 | }
|
---|
157 | }
|
---|
158 |
|
---|
159 | entry.writeEntryHeader(this.recordBuf);
|
---|
160 | this.buffer.writeRecord(this.recordBuf);
|
---|
161 |
|
---|
162 | this.currBytes = 0;
|
---|
163 |
|
---|
164 | if (entry.isDirectory()) {
|
---|
165 | this.currSize = 0;
|
---|
166 | } else {
|
---|
167 | this.currSize = (int) entry.getSize();
|
---|
168 | }
|
---|
169 | }
|
---|
170 |
|
---|
171 | /**
|
---|
172 | * Close an entry. This method MUST be called for all file
|
---|
173 | * entries that contain data. The reason is that we must
|
---|
174 | * buffer data written to the stream in order to satisfy
|
---|
175 | * the buffer's record based writes. Thus, there may be
|
---|
176 | * data fragments still being assembled that must be written
|
---|
177 | * to the output stream before this entry is closed and the
|
---|
178 | * next entry written.
|
---|
179 | */
|
---|
180 | public void closeEntry() throws IOException {
|
---|
181 | if (this.assemLen > 0) {
|
---|
182 | for (int i = this.assemLen; i < this.assemBuf.length; ++i) {
|
---|
183 | this.assemBuf[i] = 0;
|
---|
184 | }
|
---|
185 |
|
---|
186 | this.buffer.writeRecord(this.assemBuf);
|
---|
187 |
|
---|
188 | this.currBytes += this.assemLen;
|
---|
189 | this.assemLen = 0;
|
---|
190 | }
|
---|
191 |
|
---|
192 | if (this.currBytes < this.currSize) {
|
---|
193 | throw new IOException("entry closed at '" + this.currBytes
|
---|
194 | + "' before the '" + this.currSize
|
---|
195 | + "' bytes specified in the header were written");
|
---|
196 | }
|
---|
197 | }
|
---|
198 |
|
---|
199 | /**
|
---|
200 | * Writes a byte to the current tar archive entry.
|
---|
201 | *
|
---|
202 | * This method simply calls read( byte[], int, int ).
|
---|
203 | *
|
---|
204 | * @param b The byte written.
|
---|
205 | */
|
---|
206 | public void write(int b) throws IOException {
|
---|
207 | this.oneBuf[0] = (byte) b;
|
---|
208 |
|
---|
209 | this.write(this.oneBuf, 0, 1);
|
---|
210 | }
|
---|
211 |
|
---|
212 | /**
|
---|
213 | * Writes bytes to the current tar archive entry.
|
---|
214 | *
|
---|
215 | * This method simply calls write( byte[], int, int ).
|
---|
216 | *
|
---|
217 | * @param wBuf The buffer to write to the archive.
|
---|
218 | */
|
---|
219 | public void write(byte[] wBuf) throws IOException {
|
---|
220 | this.write(wBuf, 0, wBuf.length);
|
---|
221 | }
|
---|
222 |
|
---|
223 | /**
|
---|
224 | * Writes bytes to the current tar archive entry. This method
|
---|
225 | * is aware of the current entry and will throw an exception if
|
---|
226 | * you attempt to write bytes past the length specified for the
|
---|
227 | * current entry. The method is also (painfully) aware of the
|
---|
228 | * record buffering required by TarBuffer, and manages buffers
|
---|
229 | * that are not a multiple of recordsize in length, including
|
---|
230 | * assembling records from small buffers.
|
---|
231 | *
|
---|
232 | * @param wBuf The buffer to write to the archive.
|
---|
233 | * @param wOffset The offset in the buffer from which to get bytes.
|
---|
234 | * @param numToWrite The number of bytes to write.
|
---|
235 | */
|
---|
236 | public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
|
---|
237 | if ((this.currBytes + numToWrite) > this.currSize) {
|
---|
238 | throw new IOException("request to write '" + numToWrite
|
---|
239 | + "' bytes exceeds size in header of '"
|
---|
240 | + this.currSize + "' bytes");
|
---|
241 |
|
---|
242 | //
|
---|
243 | // We have to deal with assembly!!!
|
---|
244 | // The programmer can be writing little 32 byte chunks for all
|
---|
245 | // we know, and we must assemble complete records for writing.
|
---|
246 | // REVIEW Maybe this should be in TarBuffer? Could that help to
|
---|
247 | // eliminate some of the buffer copying.
|
---|
248 | //
|
---|
249 | }
|
---|
250 |
|
---|
251 | if (this.assemLen > 0) {
|
---|
252 | if ((this.assemLen + numToWrite) >= this.recordBuf.length) {
|
---|
253 | int aLen = this.recordBuf.length - this.assemLen;
|
---|
254 |
|
---|
255 | System.arraycopy(this.assemBuf, 0, this.recordBuf, 0,
|
---|
256 | this.assemLen);
|
---|
257 | System.arraycopy(wBuf, wOffset, this.recordBuf,
|
---|
258 | this.assemLen, aLen);
|
---|
259 | this.buffer.writeRecord(this.recordBuf);
|
---|
260 |
|
---|
261 | this.currBytes += this.recordBuf.length;
|
---|
262 | wOffset += aLen;
|
---|
263 | numToWrite -= aLen;
|
---|
264 | this.assemLen = 0;
|
---|
265 | } else {
|
---|
266 | System.arraycopy(wBuf, wOffset, this.assemBuf, this.assemLen,
|
---|
267 | numToWrite);
|
---|
268 |
|
---|
269 | wOffset += numToWrite;
|
---|
270 | this.assemLen += numToWrite;
|
---|
271 | numToWrite -= numToWrite;
|
---|
272 | }
|
---|
273 | }
|
---|
274 |
|
---|
275 | //
|
---|
276 | // When we get here we have EITHER:
|
---|
277 | // o An empty "assemble" buffer.
|
---|
278 | // o No bytes to write (numToWrite == 0)
|
---|
279 | //
|
---|
280 | while (numToWrite > 0) {
|
---|
281 | if (numToWrite < this.recordBuf.length) {
|
---|
282 | System.arraycopy(wBuf, wOffset, this.assemBuf, this.assemLen,
|
---|
283 | numToWrite);
|
---|
284 |
|
---|
285 | this.assemLen += numToWrite;
|
---|
286 |
|
---|
287 | break;
|
---|
288 | }
|
---|
289 |
|
---|
290 | this.buffer.writeRecord(wBuf, wOffset);
|
---|
291 |
|
---|
292 | int num = this.recordBuf.length;
|
---|
293 |
|
---|
294 | this.currBytes += num;
|
---|
295 | numToWrite -= num;
|
---|
296 | wOffset += num;
|
---|
297 | }
|
---|
298 | }
|
---|
299 |
|
---|
300 | /**
|
---|
301 | * Write an EOF (end of archive) record to the tar archive.
|
---|
302 | * An EOF record consists of a record of all zeros.
|
---|
303 | */
|
---|
304 | private void writeEOFRecord() throws IOException {
|
---|
305 | for (int i = 0; i < this.recordBuf.length; ++i) {
|
---|
306 | this.recordBuf[i] = 0;
|
---|
307 | }
|
---|
308 |
|
---|
309 | this.buffer.writeRecord(this.recordBuf);
|
---|
310 | }
|
---|
311 | }
|
---|
312 |
|
---|
313 |
|
---|