org.redline_rpm.payload.CpioHeader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redline Show documentation
Show all versions of redline Show documentation
Redline is a pure Java library for manipulating RPM Package Manager packages.
package org.redline_rpm.payload;
import org.redline_rpm.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.Date;
import static org.redline_rpm.Util.normalizePath;
/**
* This class provides a means to read file content from the compressed CPIO stream
* that is the body of an RPM distributable. Iterative calls to to read header will
* result in a header description being returned which includes a count of how many bytes
* to read from the channel for the file content.
*/
public class CpioHeader {
private final Logger LOGGER = LoggerFactory.getLogger(CpioHeader.class);
public static final int DEFAULT_FILE_PERMISSION = 0644;
public static final int DEFAULT_DIRECTORY_PERMISSION = 0755;
public static final String DEFAULT_USERNAME = "root";
public static final String DEFAULT_GROUP = "root";
public static final int FIFO = 1;
public static final int CDEV = 2;
public static final int DIR = 4;
public static final int BDEV = 6;
public static final int FILE = 8;
public static final int SYMLINK = 10;
public static final int SOCKET = 12;
protected static final int CPIO_HEADER = 110;
protected static final String MAGIC = "070701";
protected static final String TRAILER = "TRAILER!!!";
protected Charset charset = Charset.forName( "UTF-8");
protected int inode;
protected int type;
protected int permissions = DEFAULT_FILE_PERMISSION;
protected int uid;
protected String uname;
protected int gid;
protected String gname;
protected int nlink = 1;
protected long mtime;
protected int filesize;
protected int devMinor = 1;
protected int devMajor = 9;
protected int rdevMinor;
protected int rdevMajor;
protected int checksum;
protected String name;
protected int flags;
protected int verifyFlags = -1;
public CpioHeader() {
}
public CpioHeader( final String name) {
this.name = name;
}
public CpioHeader( final File file) {
this( file.getAbsolutePath(), file);
}
public CpioHeader( final String name, final URL url) {
try {
URLConnection connection = url.openConnection();
mtime = connection.getLastModified();
filesize = connection.getContentLength();
this.name = normalizePath( name);
setType( FILE);
} catch ( IOException e) {
throw new RuntimeException( e);
}
}
public CpioHeader( final String name, final File file) {
mtime = file.lastModified();
filesize = ( int ) file.length();
this.name = normalizePath( name);
if ( file.isDirectory()) setType( DIR);
else setType( FILE);
}
public int getType() { return type; }
public int getPermissions() { return permissions; }
public int getRdevMajor() { return rdevMajor; }
public int getRdevMinor() { return rdevMinor; }
public int getDevMajor() { return devMajor; }
public int getDevMinor() { return devMinor; }
public int getMtime() { return ( int) ( mtime / 1000L) ; }
public int getInode() { return inode; }
public String getName() { return name; }
public int getFlags() { return flags; }
public int getVerifyFlags() { return verifyFlags; }
public int getMode() { return ( type << 12) | ( permissions & 07777); }
public void setPermissions( int permissions) { this.permissions = permissions; }
public void setType( int type) { this.type = type; }
public void setFileSize( int filesize) { this.filesize = filesize; }
public void setMtime( long mtime) { this.mtime = mtime; }
public void setInode( int inode) { this.inode = inode; }
public void setFlags( int flags) { this.flags = flags; }
public void setVerifyFlags( int verifyFlags) { this.verifyFlags = verifyFlags; }
public String getUname() { return this.uname; }
public String getGname() { return this.gname; }
public void setUname( String uname) { this.uname = uname; }
public void setGname( String gname) { this.gname = gname; }
/**
* Test to see if this is the last header, and is therefore the end of the
* archive. Uses the CPIO magic trailer value to denote the last header of
* the stream.
* @return true if last, false if not
*/
public boolean isLast() {
return TRAILER.equals(name);
}
public void setLast() {
name = TRAILER;
}
public void setName( String name) {
this.name = name;
}
public int getFileSize() {
return filesize;
}
protected ByteBuffer writeSix( CharSequence data) {
return charset.encode( pad( data, 6));
}
protected ByteBuffer writeEight( int data) {
return charset.encode( pad( Integer.toHexString( data), 8));
}
protected CharSequence readSix( CharBuffer buffer) {
return readChars( buffer, 6);
}
protected int readEight( CharBuffer buffer) {
return Integer.parseInt( readChars( buffer, 8).toString(), 16);
}
protected CharSequence readChars( CharBuffer buffer, int length) {
if ( buffer.remaining() < length) throw new IllegalStateException( "Buffer has '" + buffer.remaining() + "' bytes but '" + length + "' are needed.");
try {
return buffer.subSequence( 0, length);
} finally {
buffer.position( buffer.position() + length);
}
}
protected String pad( CharSequence sequence, final int length) {
while ( sequence.length() < length) sequence = "0" + sequence;
return sequence.toString();
}
protected int skip( final ReadableByteChannel channel, final int total) throws IOException {
int skipped = Util.difference( total, 3);
LOGGER.debug("Skipping '{}' bytes from stream at position '{}'.",skipped,total);
Util.fill( channel, skipped);
return skipped;
}
public int skip( final WritableByteChannel channel, int total) throws IOException {
int skipped = Util.difference( total, 3);
Util.empty( channel, ByteBuffer.allocate( skipped));
LOGGER.debug("Skipping '{}' bytes from stream at position '{}'.",skipped,total);
return skipped;
}
public int read( final ReadableByteChannel channel, int total) throws IOException {
total += skip( channel, total);
ByteBuffer descriptor = Util.fill( channel, CPIO_HEADER);
CharBuffer buffer = charset.decode( descriptor);
final CharSequence magic = readSix( buffer);
if ( !MAGIC.equals(magic.toString())) throw new IllegalStateException( "Invalid magic number '" + magic + "' of length '" + magic.length() + "'.");
inode = readEight( buffer);
final int mode = readEight( buffer);
permissions = mode & 07777;
type = mode >>> 12;
uid = readEight( buffer);
gid = readEight( buffer);
nlink = readEight( buffer);
mtime = 1000L * readEight( buffer);
filesize = readEight( buffer);
devMajor = readEight( buffer);
devMinor = readEight( buffer);
rdevMajor = readEight( buffer);
rdevMinor = readEight( buffer);
int namesize = readEight( buffer);
checksum = readEight( buffer);
total += CPIO_HEADER;
name = charset.decode( Util.fill( channel, namesize - 1)).toString();
Util.fill( channel, 1);
total += namesize;
total += skip( channel, total);
return total;
}
/**
* Write the content for the CPIO header, including the name immediately following. The name data is rounded
* to the nearest 2 byte boundary as CPIO requires by appending a null when needed.
* @param channel which channel to write on
* @param total current size of header?
* @return total written and skipped
* @throws IOException there was an IO error
*/
public int write( final WritableByteChannel channel, int total) throws IOException {
final ByteBuffer buffer = charset.encode( CharBuffer.wrap( name));
int length = buffer.remaining() + 1;
ByteBuffer descriptor = ByteBuffer.allocate( CPIO_HEADER);
descriptor.put( writeSix( MAGIC));
descriptor.put( writeEight( inode));
descriptor.put( writeEight( getMode()));
descriptor.put( writeEight( uid));
descriptor.put( writeEight( gid));
descriptor.put( writeEight( nlink));
descriptor.put( writeEight(( int) ( mtime / 1000)));
descriptor.put( writeEight( filesize));
descriptor.put( writeEight( devMajor));
descriptor.put( writeEight( devMinor));
descriptor.put( writeEight( rdevMajor));
descriptor.put( writeEight( rdevMinor));
descriptor.put( writeEight( length));
descriptor.put( writeEight( checksum));
descriptor.flip();
total += CPIO_HEADER + length;
Util.empty( channel, descriptor);
Util.empty( channel, buffer);
Util.empty( channel, ByteBuffer.allocate( 1));
return total + skip( channel, total);
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append( "Inode: ").append( inode).append( "\n");
builder.append( "Permission: ").append( Integer.toString( permissions, 8)).append( "\n");
builder.append( "Type: ").append( type).append( "\n");
builder.append( "UID: ").append( uid).append( "\n");
builder.append( "GID: ").append( gid).append( "\n");
builder.append( "UserName: ").append( uname).append( "\n");
builder.append( "GroupName: ").append( gname).append( "\n");
builder.append( "Nlink: ").append( nlink).append( "\n");
builder.append( "MTime: ").append( new Date( mtime)).append( "\n");
builder.append( "FileSize: ").append( filesize).append( "\n");
builder.append( "DevMinor: ").append( devMinor).append( "\n");
builder.append( "DevMajor: ").append( devMajor).append( "\n");
builder.append( "RDevMinor: ").append( rdevMinor).append( "\n");
builder.append( "RDevMajor: ").append( rdevMajor).append( "\n");
builder.append( "NameSize: ").append( name.length() + 1).append( "\n");
builder.append( "Name: ").append( name).append( "\n");
return builder.toString();
}
}