View Javadoc

1   /*
2    * jGuild Project: jRPM
3    * Released under the Apache License ( http://www.apache.org/LICENSE )
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(); // close previous entry
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 }