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