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
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 public class CpioArchiveOutputStream extends ArchiveOutputStream<CpioArchiveEntry> implements CpioConstants {
70
71
72
73
74 private static final char NUL = '\0';
75
76 private CpioArchiveEntry entry;
77
78
79
80
81 private final short entryFormat;
82
83 private final HashMap<String, CpioArchiveEntry> names = new HashMap<>();
84
85 private long crc;
86
87 private long written;
88
89 private final int blockSize;
90
91 private long nextArtificalDeviceAndInode = 1;
92
93
94
95
96 private final ZipEncoding zipEncoding;
97
98
99 final String charsetName;
100
101
102
103
104
105
106 public CpioArchiveOutputStream(final OutputStream out) {
107 this(out, FORMAT_NEW);
108 }
109
110
111
112
113
114
115
116
117 public CpioArchiveOutputStream(final OutputStream out, final short format) {
118 this(out, format, BLOCK_SIZE, CpioUtil.DEFAULT_CHARSET_NAME);
119 }
120
121
122
123
124
125
126
127
128
129 public CpioArchiveOutputStream(final OutputStream out, final short format, final int blockSize) {
130 this(out, format, blockSize, CpioUtil.DEFAULT_CHARSET_NAME);
131 }
132
133
134
135
136
137
138
139
140
141
142 public CpioArchiveOutputStream(final OutputStream out, final short format, final int blockSize, final String encoding) {
143 super(out);
144 switch (format) {
145 case FORMAT_NEW:
146 case FORMAT_NEW_CRC:
147 case FORMAT_OLD_ASCII:
148 case FORMAT_OLD_BINARY:
149 break;
150 default:
151 throw new IllegalArgumentException("Unknown format: " + format);
152
153 }
154 this.entryFormat = format;
155 this.blockSize = blockSize;
156 this.charsetName = encoding;
157 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
158 }
159
160
161
162
163
164
165
166
167 public CpioArchiveOutputStream(final OutputStream out, final String encoding) {
168 this(out, FORMAT_NEW, BLOCK_SIZE, encoding);
169 }
170
171
172
173
174
175
176 @Override
177 public void close() throws IOException {
178 try {
179 if (!isFinished()) {
180 finish();
181 }
182 } finally {
183 super.close();
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 out.write(new byte[count]);
277 count(count);
278 }
279 }
280
281
282
283
284
285
286
287
288
289 @Override
290 public void putArchiveEntry(final CpioArchiveEntry entry) throws IOException {
291 checkFinished();
292 checkOpen();
293 if (this.entry != null) {
294 closeArchiveEntry();
295 }
296 if (entry.getTime() == -1) {
297 entry.setTime(System.currentTimeMillis() / 1000);
298 }
299
300 final short format = entry.getFormat();
301 if (format != this.entryFormat) {
302 throw new IOException("Header format: " + format + " does not match existing format: " + this.entryFormat);
303 }
304
305 if (this.names.put(entry.getName(), entry) != null) {
306 throw new IOException("Duplicate entry: " + entry.getName());
307 }
308
309 writeHeader(entry);
310 this.entry = entry;
311 this.written = 0;
312 }
313
314
315
316
317
318
319
320
321
322 @Override
323 public void write(final byte[] b, final int off, final int len) throws IOException {
324 checkOpen();
325 if (off < 0 || len < 0 || off > b.length - len) {
326 throw new IndexOutOfBoundsException();
327 }
328 if (len == 0) {
329 return;
330 }
331
332 if (this.entry == null) {
333 throw new IOException("No current CPIO entry");
334 }
335 if (this.written + len > this.entry.getSize()) {
336 throw new IOException("Attempt to write past end of STORED entry");
337 }
338 out.write(b, off, len);
339 this.written += len;
340 if (this.entry.getFormat() == FORMAT_NEW_CRC) {
341 for (int pos = 0; pos < len; pos++) {
342 this.crc += b[pos] & 0xFF;
343 this.crc &= 0xFFFFFFFFL;
344 }
345 }
346 count(len);
347 }
348
349 private void writeAsciiLong(final long number, final int length, final int radix) throws IOException {
350 final StringBuilder tmp = new StringBuilder();
351 final String tmpStr;
352 if (radix == 16) {
353 tmp.append(Long.toHexString(number));
354 } else if (radix == 8) {
355 tmp.append(Long.toOctalString(number));
356 } else {
357 tmp.append(number);
358 }
359
360 if (tmp.length() <= length) {
361 final int insertLength = length - tmp.length();
362 for (int pos = 0; pos < insertLength; pos++) {
363 tmp.insert(0, "0");
364 }
365 tmpStr = tmp.toString();
366 } else {
367 tmpStr = tmp.substring(tmp.length() - length);
368 }
369 final byte[] b = writeUsAsciiRaw(tmpStr);
370 count(b.length);
371 }
372
373 private void writeBinaryLong(final long number, final int length, final boolean swapHalfWord) throws IOException {
374 final byte[] tmp = CpioUtil.long2byteArray(number, length, swapHalfWord);
375 out.write(tmp);
376 count(tmp.length);
377 }
378
379
380
381
382
383
384
385 private void writeCString(final byte[] str) throws IOException {
386 out.write(str);
387 out.write(NUL);
388 count(str.length + 1);
389 }
390
391 private void writeHeader(final CpioArchiveEntry e) throws IOException {
392 switch (e.getFormat()) {
393 case FORMAT_NEW:
394 writeUsAsciiRaw(MAGIC_NEW);
395 count(6);
396 writeNewEntry(e);
397 break;
398 case FORMAT_NEW_CRC:
399 writeUsAsciiRaw(MAGIC_NEW_CRC);
400 count(6);
401 writeNewEntry(e);
402 break;
403 case FORMAT_OLD_ASCII:
404 writeUsAsciiRaw(MAGIC_OLD_ASCII);
405 count(6);
406 writeOldAsciiEntry(e);
407 break;
408 case FORMAT_OLD_BINARY:
409 final boolean swapHalfWord = true;
410 writeBinaryLong(MAGIC_OLD_BINARY, 2, swapHalfWord);
411 writeOldBinaryEntry(e, swapHalfWord);
412 break;
413 default:
414 throw new IOException("Unknown format " + e.getFormat());
415 }
416 }
417
418 private void writeNewEntry(final CpioArchiveEntry entry) throws IOException {
419 long inode = entry.getInode();
420 long devMin = entry.getDeviceMin();
421 if (CPIO_TRAILER.equals(entry.getName())) {
422 inode = devMin = 0;
423 } else if (inode == 0 && devMin == 0) {
424 inode = nextArtificalDeviceAndInode & 0xFFFFFFFF;
425 devMin = nextArtificalDeviceAndInode++ >> 32 & 0xFFFFFFFF;
426 } else {
427 nextArtificalDeviceAndInode = Math.max(nextArtificalDeviceAndInode, inode + 0x100000000L * devMin) + 1;
428 }
429
430 writeAsciiLong(inode, 8, 16);
431 writeAsciiLong(entry.getMode(), 8, 16);
432 writeAsciiLong(entry.getUID(), 8, 16);
433 writeAsciiLong(entry.getGID(), 8, 16);
434 writeAsciiLong(entry.getNumberOfLinks(), 8, 16);
435 writeAsciiLong(entry.getTime(), 8, 16);
436 writeAsciiLong(entry.getSize(), 8, 16);
437 writeAsciiLong(entry.getDeviceMaj(), 8, 16);
438 writeAsciiLong(devMin, 8, 16);
439 writeAsciiLong(entry.getRemoteDeviceMaj(), 8, 16);
440 writeAsciiLong(entry.getRemoteDeviceMin(), 8, 16);
441 final byte[] name = encode(entry.getName());
442 writeAsciiLong(name.length + 1L, 8, 16);
443 writeAsciiLong(entry.getChksum(), 8, 16);
444 writeCString(name);
445 pad(entry.getHeaderPadCount(name.length));
446 }
447
448 private void writeOldAsciiEntry(final CpioArchiveEntry entry) throws IOException {
449 long inode = entry.getInode();
450 long device = entry.getDevice();
451 if (CPIO_TRAILER.equals(entry.getName())) {
452 inode = device = 0;
453 } else if (inode == 0 && device == 0) {
454 inode = nextArtificalDeviceAndInode & 0777777;
455 device = nextArtificalDeviceAndInode++ >> 18 & 0777777;
456 } else {
457 nextArtificalDeviceAndInode = Math.max(nextArtificalDeviceAndInode, inode + 01000000 * device) + 1;
458 }
459
460 writeAsciiLong(device, 6, 8);
461 writeAsciiLong(inode, 6, 8);
462 writeAsciiLong(entry.getMode(), 6, 8);
463 writeAsciiLong(entry.getUID(), 6, 8);
464 writeAsciiLong(entry.getGID(), 6, 8);
465 writeAsciiLong(entry.getNumberOfLinks(), 6, 8);
466 writeAsciiLong(entry.getRemoteDevice(), 6, 8);
467 writeAsciiLong(entry.getTime(), 11, 8);
468 final byte[] name = encode(entry.getName());
469 writeAsciiLong(name.length + 1L, 6, 8);
470 writeAsciiLong(entry.getSize(), 11, 8);
471 writeCString(name);
472 }
473
474 private void writeOldBinaryEntry(final CpioArchiveEntry entry, final boolean swapHalfWord) throws IOException {
475 long inode = entry.getInode();
476 long device = entry.getDevice();
477 if (CPIO_TRAILER.equals(entry.getName())) {
478 inode = device = 0;
479 } else if (inode == 0 && device == 0) {
480 inode = nextArtificalDeviceAndInode & 0xFFFF;
481 device = nextArtificalDeviceAndInode++ >> 16 & 0xFFFF;
482 } else {
483 nextArtificalDeviceAndInode = Math.max(nextArtificalDeviceAndInode, inode + 0x10000 * device) + 1;
484 }
485
486 writeBinaryLong(device, 2, swapHalfWord);
487 writeBinaryLong(inode, 2, swapHalfWord);
488 writeBinaryLong(entry.getMode(), 2, swapHalfWord);
489 writeBinaryLong(entry.getUID(), 2, swapHalfWord);
490 writeBinaryLong(entry.getGID(), 2, swapHalfWord);
491 writeBinaryLong(entry.getNumberOfLinks(), 2, swapHalfWord);
492 writeBinaryLong(entry.getRemoteDevice(), 2, swapHalfWord);
493 writeBinaryLong(entry.getTime(), 4, swapHalfWord);
494 final byte[] name = encode(entry.getName());
495 writeBinaryLong(name.length + 1L, 2, swapHalfWord);
496 writeBinaryLong(entry.getSize(), 4, swapHalfWord);
497 writeCString(name);
498 pad(entry.getHeaderPadCount(name.length));
499 }
500
501 }