| /******************************************************************************* |
| * Copyright (c) 2004, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.wizards.datatransfer; |
| |
| import java.io.FilterOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| |
| /** |
| * Output stream for writing ustar archive files (tar) compatible |
| * with the specification in IEEE Std 1003.1-2001. |
| * |
| * @since 3.1 |
| */ |
| public class TarOutputStream extends FilterOutputStream { |
| |
| private int byteswritten = 0; |
| private int datapos = 0; |
| private long cursize = 0; |
| |
| /** |
| * Creates a new tar output stream. |
| * |
| * @param out the stream to write to |
| */ |
| public TarOutputStream(OutputStream out) { |
| super(out); |
| } |
| |
| /** |
| * Close the output stream and write any necessary padding. |
| */ |
| public void close() throws IOException { |
| // Spec says to write 1024 bytes of zeros at the end. |
| byte[] zeros = new byte[1024]; |
| cursize = 1024; |
| write(zeros, 0, 1024); |
| |
| // Default block size for tar files is 10240, so we have to |
| // pad the end of the file to be a multiple of this size. |
| if((byteswritten % 10240) != 0) { |
| int length = 10240 - (byteswritten % 10240); |
| cursize = length; |
| zeros = new byte[length]; |
| write(zeros, 0, length); |
| } |
| super.close(); |
| } |
| |
| /** |
| * Close the current entry in the tar file. Must be called |
| * after each entry is completed. |
| * |
| * @throws IOException |
| */ |
| public void closeEntry() throws IOException { |
| byte[] data = new byte[512]; |
| int len = 512 - datapos; |
| if(len > 0 && datapos > 0) { |
| cursize = len; |
| write(data, 0, len); |
| } |
| } |
| |
| /** |
| * The checksum of a tar file header is simply the sum of the bytes in |
| * the header. |
| * |
| * @param header |
| * @return checksum |
| */ |
| private long headerChecksum(byte[] header) { |
| long sum = 0; |
| for(int i = 0; i < 512; i++) { |
| sum += header[i] & 0xff; |
| } |
| return sum; |
| } |
| |
| /** |
| * Adds an entry for a new file in the tar archive. |
| * |
| * @param e TarEntry describing the file |
| * @throws IOException |
| */ |
| public void putNextEntry(TarEntry e) throws IOException { |
| byte[] header = new byte[512]; |
| String filename = e.getName(); |
| String prefix = null; |
| int pos, i; |
| |
| /* Split filename into name and prefix if necessary. */ |
| byte[] filenameBytes = filename.getBytes("UTF8"); //$NON-NLS-1$ |
| if (filenameBytes.length > 99) { |
| int seppos = filename.lastIndexOf('/'); |
| if(seppos == -1) throw new IOException("filename too long"); //$NON-NLS-1$ |
| prefix = filename.substring(0, seppos); |
| filename = filename.substring(seppos + 1); |
| filenameBytes = filename.getBytes("UTF8"); //$NON-NLS-1$ |
| if (filenameBytes.length > 99) |
| throw new IOException("filename too long"); //$NON-NLS-1$ |
| } |
| |
| /* Filename. */ |
| pos = 0; |
| System.arraycopy(filenameBytes, 0, header, 0, filenameBytes.length); |
| pos += 100; |
| |
| /* File mode. */ |
| StringBuffer mode = new StringBuffer(Long.toOctalString(e.getMode())); |
| while(mode.length() < 7) mode.insert(0,'0'); |
| for(i = 0; i < 7; i++) { |
| header[pos + i] = (byte) mode.charAt(i); |
| } |
| pos += 8; |
| |
| /* UID. */ |
| header[pos] = '0'; |
| pos += 8; |
| |
| /* GID. */ |
| header[pos] = '0'; |
| pos += 8; |
| |
| /* Length of the file. */ |
| String length = Long.toOctalString(e.getSize()); |
| for(i = 0; i < length.length(); i++) { |
| header[pos + i] = (byte) length.charAt(i); |
| } |
| pos += 12; |
| |
| /* mtime */ |
| String mtime = Long.toOctalString(e.getTime()); |
| for(i = 0; i < mtime.length(); i++) { |
| header[pos + i] = (byte) mtime.charAt(i); |
| } |
| pos += 12; |
| |
| /* "Blank" out the checksum. */ |
| for(i = 0; i < 8; i++) { |
| header[pos + i] = ' '; |
| } |
| pos += 8; |
| |
| /* Link flag. */ |
| header[pos] = (byte) e.getFileType(); |
| pos += 1; |
| |
| /* Link destination. */ |
| pos += 100; |
| |
| /* Add ustar header. */ |
| String ustar = "ustar 00"; //$NON-NLS-1$ |
| for(i = 0; i < ustar.length(); i++) { |
| header[pos + i] = (byte) ustar.charAt(i); |
| } |
| header[pos + 5] = 0; |
| pos += 8; |
| |
| /* Username. */ |
| String uname = "nobody"; //$NON-NLS-1$ |
| for(i = 0; i < uname.length(); i++) { |
| header[pos + i] = (byte) uname.charAt(i); |
| } |
| pos += 32; |
| |
| |
| /* Group name. */ |
| String gname = "nobody"; //$NON-NLS-1$ |
| for(i = 0; i < gname.length(); i++) { |
| header[pos + i] = (byte) gname.charAt(i); |
| } |
| pos += 32; |
| |
| /* Device major. */ |
| pos += 8; |
| |
| /* Device minor. */ |
| pos += 8; |
| |
| /* File prefix. */ |
| if(prefix != null) { |
| byte[] prefixBytes = prefix.getBytes("UTF8"); //$NON-NLS-1$ |
| if (prefixBytes.length > 155) |
| throw new IOException("prefix too large"); //$NON-NLS-1$ |
| System.arraycopy(prefixBytes, 0, header, pos, prefixBytes.length); |
| } |
| |
| long sum = headerChecksum(header); |
| pos = 100 + 8 + 8 + 8 + 12 + 12; |
| String sumval = Long.toOctalString(sum); |
| for(i = 0; i < sumval.length(); i++) { |
| header[pos + i] = (byte) sumval.charAt(i); |
| } |
| |
| cursize = 512; |
| write(header, 0, 512); |
| |
| cursize = e.getSize(); |
| } |
| |
| /** |
| * Writes data for the current file into the archive. |
| */ |
| public void write(byte[] b, int off, int len) throws IOException { |
| super.write(b, off, len); |
| datapos = (datapos + len) % 512; |
| byteswritten += len; |
| cursize -= len; |
| if(cursize < 0) { |
| throw new IOException("too much data written for current file"); //$NON-NLS-1$ |
| } |
| } |
| } |