1
2
3
4
5 package com.jguild.jrpm.io.cpio;
6
7 import java.io.FilterOutputStream;
8 import java.io.IOException;
9 import java.io.OutputStream;
10 import java.util.HashMap;
11
12 /***
13 * CPIOOutputStream is a stream for writting cpio streams.
14 * All formats of cpio are supported (old ascii, old binary, new portable format
15 * and the new portable format with crc).
16 *
17 * <p>
18 * An entry can be written by creating an instance of CPIOEntry and fill it with
19 * the necessary values and put it into the cpio stream. Afterwards write the
20 * contents of the file into the cpio stream. Either close the stream by calling
21 * finish() or put a next entry into the cpio stream.
22 * <p>
23 * <code><pre>
24 * CPIOOutputStream cpioOut = new CPIOOutputStream(new BufferedOutputStream(new FileOutputStream(new File("test.cpio"))));
25 * CPIOEntry cpioEntry = new CPIOEntry();
26 * cpioEntry.setName("testfile");
27 * String testContents = "12345";
28 * cpioEntry.setFileSize(testContents.length());
29 * cpioOut.putNextEntry(cpioEntry);
30 * cpioOut.write(testContents.getBytes());
31 * cpioOut.finish();
32 * cpioOut.close();
33 * </pre></code>
34 *
35 * Note: This implementation should be compatible to cpio 2.5
36 *
37 * @author Michael Kuss
38 */
39 public class CPIOOutputStream extends FilterOutputStream implements
40 CPIOConstants {
41 private CPIOEntry cpioEntry;
42 private boolean closed = false;
43 private boolean finished;
44 private short entryFormat = FORMAT_NEW;
45 private HashMap names = new HashMap();
46 private long crc = 0;
47 private long written;
48
49 /***
50 * Check to make sure that this stream has not been closed
51 *
52 * @exception IOException if the stream is already closed
53 */
54 private void ensureOpen() throws IOException {
55 if (closed) { throw new IOException("Stream closed"); }
56 }
57
58 /***
59 * Construct the cpio output stream with a specified format
60 *
61 * @param out The cpio stream
62 * @param format The format of the stream
63 */
64 public CPIOOutputStream(OutputStream out, short format) {
65 super(out);
66 setFormat(format);
67 }
68
69 /***
70 * Construct the cpio output stream.
71 * The format for this CPIO stream is the "new" format
72 *
73 * @param out The cpio stream
74 */
75 public CPIOOutputStream(OutputStream out) {
76 this(out, FORMAT_NEW);
77 }
78
79 /***
80 * Set a default header format. This will be used if no format
81 * is defined in the cpioEntry given to putNextEntry().
82 *
83 * @param format A CPIO format
84 */
85 public void setFormat(short format) {
86 switch (format) {
87 case FORMAT_NEW:
88 case FORMAT_NEW_CRC:
89 case FORMAT_OLD_ASCII:
90 case FORMAT_OLD_BINARY:
91 break;
92 default:
93 throw new IllegalArgumentException("Unknown header type");
94
95 }
96 this.entryFormat = format;
97 }
98
99 /***
100 * Begins writing a new CPIO file entry and positions the stream to the
101 * start of the entry data. Closes the current entry if still active.
102 * The current time will be used if the entry has no set modification
103 * time and the default header format will be used if no other format
104 * is specified in the entry.
105 *
106 * @param e the CPIO cpioEntry to be written
107 * @exception IOException if an I/O error has occurred or
108 * if a CPIO file error has occurred
109 */
110 public void putNextEntry(CPIOEntry e) throws IOException {
111 ensureOpen();
112 if (cpioEntry != null) {
113 closeEntry();
114 }
115 if (e.getTime() == -1) {
116 e.setTime(System.currentTimeMillis());
117 }
118 if (e.getFormat() == -1) {
119 e.setFormat(entryFormat);
120 }
121
122 if (names.put(e.getName(), e) != null) { throw new IOException(
123 "duplicate entry: " + e.getName()); }
124
125 writeHeader(e);
126 cpioEntry = e;
127 written = 0;
128 }
129
130 private void writeHeader(CPIOEntry e) throws IOException {
131 switch (e.getFormat()) {
132 case FORMAT_NEW:
133 out.write(MAGIC_NEW.getBytes());
134 writeNewEntry(e);
135 break;
136 case FORMAT_NEW_CRC:
137 out.write(MAGIC_NEW_CRC.getBytes());
138 writeNewEntry(e);
139 break;
140 case FORMAT_OLD_ASCII:
141 out.write(MAGIC_OLD_ASCII.getBytes());
142 writeOldAsciiEntry(e);
143 break;
144 case FORMAT_OLD_BINARY:
145 boolean swapHalfWord = true;
146 writeBinaryLong(MAGIC_OLD_BINARY, 2, swapHalfWord);
147 writeOldBinaryEntry(e, swapHalfWord);
148 break;
149 }
150 }
151
152 private void writeNewEntry(CPIOEntry entry) throws IOException {
153 writeAsciiLong(entry.getInode(), 8, 16);
154 writeAsciiLong(entry.getMode(), 8, 16);
155 writeAsciiLong(entry.getUID(), 8, 16);
156 writeAsciiLong(entry.getGID(), 8, 16);
157 writeAsciiLong(entry.getNumberOfLinks(), 8, 16);
158 writeAsciiLong(entry.getTime(), 8, 16);
159 writeAsciiLong(entry.getFileSize(), 8, 16);
160 writeAsciiLong(entry.getDeviceMaj(), 8, 16);
161 writeAsciiLong(entry.getDeviceMin(), 8, 16);
162 writeAsciiLong(entry.getRemoteDeviceMaj(), 8, 16);
163 writeAsciiLong(entry.getRemoteDeviceMin(), 8, 16);
164 writeAsciiLong(entry.getName().length() + 1, 8, 16);
165 writeAsciiLong(entry.getChksum(), 8, 16);
166 writeCString(entry.getName());
167 pad(entry.getHeaderSize() + entry.getName().length() + 1, 4);
168 }
169
170 private void writeOldAsciiEntry(CPIOEntry entry) throws IOException {
171 writeAsciiLong(entry.getDevice(), 6, 8);
172 writeAsciiLong(entry.getInode(), 6, 8);
173 writeAsciiLong(entry.getMode(), 6, 8);
174 writeAsciiLong(entry.getUID(), 6, 8);
175 writeAsciiLong(entry.getGID(), 6, 8);
176 writeAsciiLong(entry.getNumberOfLinks(), 6, 8);
177 writeAsciiLong(entry.getRemoteDevice(), 6, 8);
178 writeAsciiLong(entry.getTime(), 11, 8);
179 writeAsciiLong(entry.getName().length() + 1, 6, 8);
180 writeAsciiLong(entry.getFileSize(), 11, 8);
181 writeCString(entry.getName());
182 }
183
184 private void writeOldBinaryEntry(CPIOEntry entry, boolean swapHalfWord)
185 throws IOException {
186 writeBinaryLong(entry.getDevice(), 2, swapHalfWord);
187 writeBinaryLong(entry.getInode(), 2, swapHalfWord);
188 writeBinaryLong(entry.getMode(), 2, swapHalfWord);
189 writeBinaryLong(entry.getUID(), 2, swapHalfWord);
190 writeBinaryLong(entry.getGID(), 2, swapHalfWord);
191 writeBinaryLong(entry.getNumberOfLinks(), 2, swapHalfWord);
192 writeBinaryLong(entry.getRemoteDevice(), 2, swapHalfWord);
193 writeBinaryLong(entry.getTime(), 4, swapHalfWord);
194 writeBinaryLong(entry.getName().length() + 1, 2, swapHalfWord);
195 writeBinaryLong(entry.getFileSize(), 4, swapHalfWord);
196 writeCString(entry.getName());
197 pad(entry.getHeaderSize() + entry.getName().length() + 1, 2);
198 }
199
200 /***
201 * Closes the current CPIO entry and positions the stream for writing
202 * the next entry.
203 * @exception IOException if an I/O error has occurred or
204 * if a CPIO file error has occurred
205 */
206 public void closeEntry() throws IOException {
207 ensureOpen();
208
209 if (cpioEntry.getFileSize() != written) { throw new IOException(
210 "invalid entry size (expected " + cpioEntry.getFileSize()
211 + " but got " + written + " bytes)"); }
212 if ((cpioEntry.getFormat() | FORMAT_NEW_MASK) == FORMAT_NEW_MASK) pad(
213 cpioEntry.getFileSize(), 4);
214 else if ((cpioEntry.getFormat() | FORMAT_OLD_BINARY) == FORMAT_OLD_BINARY)
215 pad(cpioEntry.getFileSize(), 2);
216 if ((cpioEntry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
217 if (crc != cpioEntry.getChksum())
218 throw new IOException("CRC Error");
219 }
220 if (cpioEntry != null) {
221 cpioEntry = null;
222 }
223 crc = 0;
224 written = 0;
225 }
226
227 /***
228 * Writes an array of bytes to the current CPIO entry data. This method
229 * will block until all the bytes are written.
230 * @param b the data to be written
231 * @param off the start offset in the data
232 * @param len the number of bytes that are written
233 * @exception IOException if an I/O error has occurred or
234 * if a CPIO file error has occurred
235 */
236 public synchronized void write(byte[] b, int off, int len)
237 throws IOException {
238 ensureOpen();
239 if (off < 0 || len < 0 || off > b.length - len) {
240 throw new IndexOutOfBoundsException();
241 } else if (len == 0) { return; }
242
243 if (cpioEntry == null) { throw new IOException("no current CPIO entry"); }
244 if (written + len > cpioEntry.getFileSize()) { throw new IOException(
245 "attempt to write past end of STORED entry"); }
246 out.write(b, off, len);
247 written += len;
248 if ((cpioEntry.getFormat() | FORMAT_NEW_CRC) == FORMAT_NEW_CRC) {
249 for (int pos = 0; pos < len; pos++)
250 crc += b[pos] & 0xFF;
251 }
252 }
253
254 /***
255 * Finishes writing the contents of the CPIO output stream without closing
256 * the underlying stream. Use this method when applying multiple filters
257 * in succession to the same output stream.
258 * @exception IOException if an I/O exception has occurred or
259 * if a CPIO file error has occurred
260 */
261 public void finish() throws IOException {
262 ensureOpen();
263 if (finished) { return; }
264 if (cpioEntry != null) {
265 closeEntry();
266 }
267 cpioEntry = new CPIOEntry(entryFormat);
268 cpioEntry.setMode(0);
269 cpioEntry.setName("TRAILER!!!");
270 cpioEntry.setNumberOfLinks(1);
271 writeHeader(cpioEntry);
272 closeEntry();
273 }
274
275 /***
276 * Closes the CPIO output stream as well as the stream being filtered.
277 * @exception IOException if an I/O error has occurred or
278 * if a CPIO file error has occurred
279 */
280 public void close() throws IOException {
281 if (!closed) {
282 super.close();
283 closed = true;
284 }
285 }
286
287 private void pad(long count, int border) throws IOException {
288 long skip = count % border;
289 if (skip > 0) {
290 byte tmp[] = new byte[(int) (border - skip)];
291 out.write(tmp);
292 }
293 }
294
295 private void writeBinaryLong(long number, int length, boolean swapHalfWord)
296 throws IOException {
297 byte tmp[] = long2byteArray(number, length, swapHalfWord);
298 out.write(tmp);
299 }
300
301 private void writeAsciiLong(long number, int length, int radix)
302 throws IOException {
303 StringBuffer tmp = new StringBuffer();
304 String tmpStr;
305 if (radix == 16) tmp.append(Long.toHexString(number));
306 else if (radix == 8) tmp.append(Long.toOctalString(number));
307 else
308 tmp.append(Long.toString(number));
309
310 if (tmp.length() <= length) {
311 long insertLength = length - tmp.length();
312 for (int pos = 0; pos < insertLength; pos++) {
313 tmp.insert(0, "0");
314 }
315 tmpStr = tmp.toString();
316 } else {
317 tmpStr = tmp.substring(tmp.length() - length);
318 }
319 out.write(tmpStr.getBytes());
320 }
321
322 private void writeCString(String str) throws IOException {
323 out.write(str.getBytes());
324 out.write('\0');
325 }
326
327 /***
328 * Converts a byte array to a long.
329 * Halfwords can be swaped with setting swapHalfWord=true.
330 *
331 * @param number An array of bytes containing a number
332 * @param length The length of the returned array
333 * @param swapHalfWord Swap halfwords ([0][1][2][3]->[1][0][3][2])
334 * @return The long value
335 */
336 private byte[] long2byteArray(long number, int length, boolean swapHalfWord) {
337 byte[] ret = new byte[length];
338 int pos = 0;
339 long tmp_number = 0;
340
341 if (length % 2 != 0 || length < 2) { throw new UnsupportedOperationException(); }
342
343 tmp_number = number;
344 for (pos = length - 1; pos >= 0; pos--) {
345 ret[pos] = (byte) (tmp_number & 0xFF);
346 tmp_number >>= 8;
347 }
348
349 if (!swapHalfWord) {
350 byte tmp = 0;
351 for (pos = 0; pos < length; pos++) {
352 tmp = ret[pos];
353 ret[pos++] = ret[pos];
354 ret[pos] = tmp;
355 }
356 }
357
358 return ret;
359 }
360 }