1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.archivers.cpio;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.file.LinkOption;
26 import java.nio.file.Path;
27 import java.util.Arrays;
28 import java.util.HashMap;
29
30 import org.apache.commons.compress.archivers.ArchiveOutputStream;
31 import org.apache.commons.compress.archivers.zip.ZipEncoding;
32 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
33 import org.apache.commons.compress.utils.ArchiveUtils;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class CpioArchiveOutputStream extends ArchiveOutputStream<CpioArchiveEntry> implements CpioConstants {
71
72 private CpioArchiveEntry entry;
73
74 private boolean closed;
75
76
77 private boolean finished;
78
79
80
81
82 private final short entryFormat;
83
84 private final HashMap<String, CpioArchiveEntry> names = new HashMap<>();
85
86 private long crc;
87
88 private long written;
89
90 private final OutputStream out;
91
92 private final int blockSize;
93
94 private long nextArtificalDeviceAndInode = 1;
95
96
97
98
99 private final ZipEncoding zipEncoding;
100
101
102 final String charsetName;
103
104
105
106
107
108
109 public CpioArchiveOutputStream(final OutputStream out) {
110 this(out, FORMAT_NEW);
111 }
112
113
114
115
116
117
118
119
120 public CpioArchiveOutputStream(final OutputStream out, final short format) {
121 this(out, format, BLOCK_SIZE, CpioUtil.DEFAULT_CHARSET_NAME);
122 }
123
124
125
126
127
128
129
130
131
132
133 public CpioArchiveOutputStream(final OutputStream out, final short format, final int blockSize) {
134 this(out, format, blockSize, CpioUtil.DEFAULT_CHARSET_NAME);
135 }
136
137
138
139
140
141
142
143
144
145
146
147 public CpioArchiveOutputStream(final OutputStream out, final short format, final int blockSize, final String encoding) {
148 this.out = out;
149 switch (format) {
150 case FORMAT_NEW:
151 case FORMAT_NEW_CRC:
152 case FORMAT_OLD_ASCII:
153 case FORMAT_OLD_BINARY:
154 break;
155 default:
156 throw new IllegalArgumentException("Unknown format: " + format);
157
158 }
159 this.entryFormat = format;
160 this.blockSize = blockSize;
161 this.charsetName = encoding;
162 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
163 }
164
165
166
167
168
169
170
171
172 public CpioArchiveOutputStream(final OutputStream out, final String encoding) {
173 this(out, FORMAT_NEW, BLOCK_SIZE, encoding);
174 }
175
176
177
178
179
180
181 @Override
182 public void close() throws IOException {
183 try {
184 if (!finished) {
185 finish();
186 }
187 } finally {
188 if (!this.closed) {
189 out.close();
190 this.closed = true;
191 }
192 }
193 }
194
195
196
197
198
199
200 @Override
201 public void closeArchiveEntry() throws IOException {
202 if (finished) {
203 throw new IOException("Stream has already been finished");
204 }
205
206 ensureOpen();
207
208 if (entry == null) {
209 throw new IOException("Trying to close non-existent entry");
210 }
211
212 if (this.entry.getSize() != this.written) {
213 throw new IOException("Invalid entry size (expected " + this.entry.getSize() + " but got " + this.written + " bytes)");
214 }
215 pad(this.entry.getDataPadCount());
216 if (this.entry.getFormat() == FORMAT_NEW_CRC && this.crc != this.entry.getChksum()) {
217 throw new IOException("CRC Error");
218 }
219 this.entry = null;
220 this.crc = 0;
221 this.written = 0;
222 }
223
224
225
226
227
228
229 @Override
230 public CpioArchiveEntry createArchiveEntry(final File inputFile, final String entryName) throws IOException {
231 if (finished) {
232 throw new IOException("Stream has already been finished");
233 }
234 return new CpioArchiveEntry(inputFile, entryName);
235 }
236
237
238
239
240
241
242 @Override
243 public CpioArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
244 if (finished) {
245 throw new IOException("Stream has already been finished");
246 }
247 return new CpioArchiveEntry(inputPath, entryName, options);
248 }
249
250
251
252
253
254
255
256
257 private byte[] encode(final String str) throws IOException {
258 final ByteBuffer buf = zipEncoding.encode(str);
259 final int len = buf.limit() - buf.position();
260 return Arrays.copyOfRange(buf.array(), buf.arrayOffset(), buf.arrayOffset() + len);
261 }
262
263
264
265
266
267
268 private void ensureOpen() throws IOException {
269 if (this.closed) {
270 throw new IOException("Stream closed");
271 }
272 }
273
274
275
276
277
278
279
280 @Override
281 public void finish() throws IOException {
282 ensureOpen();
283 if (finished) {
284 throw new IOException("This archive has already been finished");
285 }
286
287 if (this.entry != null) {
288 throw new IOException("This archive contains unclosed entries.");
289 }
290 this.entry = new CpioArchiveEntry(this.entryFormat);
291 this.entry.setName(CPIO_TRAILER);
292 this.entry.setNumberOfLinks(1);
293 writeHeader(this.entry);
294 closeArchiveEntry();
295
296 final int lengthOfLastBlock = (int) (getBytesWritten() % blockSize);
297 if (lengthOfLastBlock != 0) {
298 pad(blockSize - lengthOfLastBlock);
299 }
300
301 finished = true;
302 }
303
304 private void pad(final int count) throws IOException {
305 if (count > 0) {
306 final byte[] buff = new byte[count];
307 out.write(buff);
308 count(count);
309 }
310 }
311
312
313
314
315
316
317
318
319
320 @Override
321 public void putArchiveEntry(final CpioArchiveEntry entry) throws IOException {
322 if (finished) {
323 throw new IOException("Stream has already been finished");
324 }
325
326 ensureOpen();
327 if (this.entry != null) {
328 closeArchiveEntry();
329 }
330 if (entry.getTime() == -1) {
331 entry.setTime(System.currentTimeMillis() / 1000);
332 }
333
334 final short format = entry.getFormat();
335 if (format != this.entryFormat) {
336 throw new IOException("Header format: " + format + " does not match existing format: " + this.entryFormat);
337 }
338
339 if (this.names.put(entry.getName(), entry) != null) {
340 throw new IOException("Duplicate entry: " + entry.getName());
341 }
342
343 writeHeader(entry);
344 this.entry = entry;
345 this.written = 0;
346 }
347
348
349
350
351
352
353
354
355
356 @Override
357 public void write(final byte[] b, final int off, final int len) throws IOException {
358 ensureOpen();
359 if (off < 0 || len < 0 || off > b.length - len) {
360 throw new IndexOutOfBoundsException();
361 }
362 if (len == 0) {
363 return;
364 }
365
366 if (this.entry == null) {
367 throw new IOException("No current CPIO entry");
368 }
369 if (this.written + len > this.entry.getSize()) {
370 throw new IOException("Attempt to write past end of STORED entry");
371 }
372 out.write(b, off, len);
373 this.written += len;
374 if (this.entry.getFormat() == FORMAT_NEW_CRC) {
375 for (int pos = 0; pos < len; pos++) {
376 this.crc += b[pos] & 0xFF;
377 this.crc &= 0xFFFFFFFFL;
378 }
379 }
380 count(len);
381 }
382
383 private void writeAsciiLong(final long number, final int length, final int radix) throws IOException {
384 final StringBuilder tmp = new StringBuilder();
385 final String tmpStr;
386 if (radix == 16) {
387 tmp.append(Long.toHexString(number));
388 } else if (radix == 8) {
389 tmp.append(Long.toOctalString(number));
390 } else {
391 tmp.append(number);
392 }
393
394 if (tmp.length() <= length) {
395 final int insertLength = length - tmp.length();
396 for (int pos = 0; pos < insertLength; pos++) {
397 tmp.insert(0, "0");
398 }
399 tmpStr = tmp.toString();
400 } else {
401 tmpStr = tmp.substring(tmp.length() - length);
402 }
403 final byte[] b = ArchiveUtils.toAsciiBytes(tmpStr);
404 out.write(b);
405 count(b.length);
406 }
407
408 private void writeBinaryLong(final long number, final int length, final boolean swapHalfWord) throws IOException {
409 final byte[] tmp = CpioUtil.long2byteArray(number, length, swapHalfWord);
410 out.write(tmp);
411 count(tmp.length);
412 }
413
414
415
416
417
418
419
420 private void writeCString(final byte[] str) throws IOException {
421 out.write(str);
422 out.write('\0');
423 count(str.length + 1);
424 }
425
426 private void writeHeader(final CpioArchiveEntry e) throws IOException {
427 switch (e.getFormat()) {
428 case FORMAT_NEW:
429 out.write(ArchiveUtils.toAsciiBytes(MAGIC_NEW));
430 count(6);
431 writeNewEntry(e);
432 break;
433 case FORMAT_NEW_CRC:
434 out.write(ArchiveUtils.toAsciiBytes(MAGIC_NEW_CRC));
435 count(6);
436 writeNewEntry(e);
437 break;
438 case FORMAT_OLD_ASCII:
439 out.write(ArchiveUtils.toAsciiBytes(MAGIC_OLD_ASCII));
440 count(6);
441 writeOldAsciiEntry(e);
442 break;
443 case FORMAT_OLD_BINARY:
444 final boolean swapHalfWord = true;
445 writeBinaryLong(MAGIC_OLD_BINARY, 2, swapHalfWord);
446 writeOldBinaryEntry(e, swapHalfWord);
447 break;
448 default:
449 throw new IOException("Unknown format " + e.getFormat());
450 }
451 }
452
453 private void writeNewEntry(final CpioArchiveEntry entry) throws IOException {
454 long inode = entry.getInode();
455 long devMin = entry.getDeviceMin();
456 if (CPIO_TRAILER.equals(entry.getName())) {
457 inode = devMin = 0;
458 } else if (inode == 0 && devMin == 0) {
459 inode = nextArtificalDeviceAndInode & 0xFFFFFFFF;
460 devMin = nextArtificalDeviceAndInode++ >> 32 & 0xFFFFFFFF;
461 } else {
462 nextArtificalDeviceAndInode = Math.max(nextArtificalDeviceAndInode, inode + 0x100000000L * devMin) + 1;
463 }
464
465 writeAsciiLong(inode, 8, 16);
466 writeAsciiLong(entry.getMode(), 8, 16);
467 writeAsciiLong(entry.getUID(), 8, 16);
468 writeAsciiLong(entry.getGID(), 8, 16);
469 writeAsciiLong(entry.getNumberOfLinks(), 8, 16);
470 writeAsciiLong(entry.getTime(), 8, 16);
471 writeAsciiLong(entry.getSize(), 8, 16);
472 writeAsciiLong(entry.getDeviceMaj(), 8, 16);
473 writeAsciiLong(devMin, 8, 16);
474 writeAsciiLong(entry.getRemoteDeviceMaj(), 8, 16);
475 writeAsciiLong(entry.getRemoteDeviceMin(), 8, 16);
476 final byte[] name = encode(entry.getName());
477 writeAsciiLong(name.length + 1L, 8, 16);
478 writeAsciiLong(entry.getChksum(), 8, 16);
479 writeCString(name);
480 pad(entry.getHeaderPadCount(name.length));
481 }
482
483 private void writeOldAsciiEntry(final CpioArchiveEntry entry) throws IOException {
484 long inode = entry.getInode();
485 long device = entry.getDevice();
486 if (CPIO_TRAILER.equals(entry.getName())) {
487 inode = device = 0;
488 } else if (inode == 0 && device == 0) {
489 inode = nextArtificalDeviceAndInode & 0777777;
490 device = nextArtificalDeviceAndInode++ >> 18 & 0777777;
491 } else {
492 nextArtificalDeviceAndInode = Math.max(nextArtificalDeviceAndInode, inode + 01000000 * device) + 1;
493 }
494
495 writeAsciiLong(device, 6, 8);
496 writeAsciiLong(inode, 6, 8);
497 writeAsciiLong(entry.getMode(), 6, 8);
498 writeAsciiLong(entry.getUID(), 6, 8);
499 writeAsciiLong(entry.getGID(), 6, 8);
500 writeAsciiLong(entry.getNumberOfLinks(), 6, 8);
501 writeAsciiLong(entry.getRemoteDevice(), 6, 8);
502 writeAsciiLong(entry.getTime(), 11, 8);
503 final byte[] name = encode(entry.getName());
504 writeAsciiLong(name.length + 1L, 6, 8);
505 writeAsciiLong(entry.getSize(), 11, 8);
506 writeCString(name);
507 }
508
509 private void writeOldBinaryEntry(final CpioArchiveEntry entry, final boolean swapHalfWord) throws IOException {
510 long inode = entry.getInode();
511 long device = entry.getDevice();
512 if (CPIO_TRAILER.equals(entry.getName())) {
513 inode = device = 0;
514 } else if (inode == 0 && device == 0) {
515 inode = nextArtificalDeviceAndInode & 0xFFFF;
516 device = nextArtificalDeviceAndInode++ >> 16 & 0xFFFF;
517 } else {
518 nextArtificalDeviceAndInode = Math.max(nextArtificalDeviceAndInode, inode + 0x10000 * device) + 1;
519 }
520
521 writeBinaryLong(device, 2, swapHalfWord);
522 writeBinaryLong(inode, 2, swapHalfWord);
523 writeBinaryLong(entry.getMode(), 2, swapHalfWord);
524 writeBinaryLong(entry.getUID(), 2, swapHalfWord);
525 writeBinaryLong(entry.getGID(), 2, swapHalfWord);
526 writeBinaryLong(entry.getNumberOfLinks(), 2, swapHalfWord);
527 writeBinaryLong(entry.getRemoteDevice(), 2, swapHalfWord);
528 writeBinaryLong(entry.getTime(), 4, swapHalfWord);
529 final byte[] name = encode(entry.getName());
530 writeBinaryLong(name.length + 1L, 2, swapHalfWord);
531 writeBinaryLong(entry.getSize(), 4, swapHalfWord);
532 writeCString(name);
533 pad(entry.getHeaderPadCount(name.length));
534 }
535
536 }