Package org.redline_rpm

Source Code of org.redline_rpm.Builder

package org.redline_rpm;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;

import org.bouncycastle.openpgp.PGPPrivateKey;
import org.redline_rpm.header.Architecture;
import org.redline_rpm.header.Format;
import org.redline_rpm.header.Os;
import org.redline_rpm.header.RpmType;
import org.redline_rpm.payload.Contents;
import org.redline_rpm.payload.CpioHeader;
import org.redline_rpm.payload.Directive;

import static org.redline_rpm.ChannelWrapper.*;
import static org.redline_rpm.header.AbstractHeader.*;
import static org.redline_rpm.header.Flags.*;
import static org.redline_rpm.header.Signature.SignatureTag.*;
import static org.redline_rpm.header.Header.HeaderTag.*;

/**
* The normal entry point to the API used for
* building and RPM. The API provides methods to
* configure and add contents to a new RPM. The
* current version of the RPM format (3.0) requires
* numerous headers to be set for an RPM to be
* valid. All of the required fields are either
* set automatically or exposed through setters in
* this builder class. Any required fields are
* marked in their respective method API documentation.
*/
public class Builder {

  private static final int GPGSIZE = 65;
  private static final int DSASIZE = 65;
  private static final int SHASIZE = 41;
  private static final int MD5SIZE = 32;

  private static final String DEFAULTSCRIPTPROG = "/bin/sh";

  private static final char[] ILLEGAL_CHARS_VARIABLE = new char[] { '-', '~', '/' };
  private static final char[] ILLEGAL_CHARS_NAME = new char[] { '/', ' ', '\t', '\n', '\r' };

  protected final Format format = new Format();
  protected final Set< PrivateKey> signatures = new HashSet< PrivateKey>();
  protected final Map< String, CharSequence> dependencies = new LinkedHashMap< String, CharSequence>();
  protected final Map< String, Integer> flags = new LinkedHashMap< String, Integer>();

  protected final Map<String, CharSequence> obsoletes = new LinkedHashMap<String, CharSequence>();
  protected final Map<String, Integer> obsoletesFlags = new LinkedHashMap<String, Integer>();
 
  protected final Map<String, CharSequence> conflicts = new LinkedHashMap<String, CharSequence>();
  protected final Map<String, Integer> conflictsFlags = new LinkedHashMap<String, Integer>();
 
  protected final List< String> triggerscripts = new LinkedList< String>();
  protected final List< String> triggerscriptprogs = new LinkedList< String>();

  protected final List< String> triggernames = new LinkedList< String>();
  protected final List< String> triggerversions = new LinkedList< String>();
  protected final List< Integer> triggerflags = new LinkedList< Integer>();
  protected final List< Integer> triggerindexes = new LinkedList< Integer>();

  private int triggerCounter = 0;

  @SuppressWarnings( "unchecked")
  protected final Entry< byte[]> signature = ( Entry< byte[]>) format.getSignature().addEntry( SIGNATURES, 16);
 
  @SuppressWarnings( "unchecked")
  protected final Entry< byte[]> immutable = ( Entry< byte[]>) format.getHeader().addEntry( HEADERIMMUTABLE, 16);

  protected Contents contents = new Contents();
    protected File privateKeyRingFile;
    protected String privateKeyId;
    protected String privateKeyPassphrase;
    protected PGPPrivateKey privateKey;

  /**
   * Initializes the builder and sets some required fields to known values.
   */
  public Builder() {
    format.getHeader().createEntry( HEADERI18NTABLE, "C");
    format.getHeader().createEntry( BUILDTIME, ( int) ( System.currentTimeMillis() / 1000));
    format.getHeader().createEntry( RPMVERSION, "4.4.2");
    format.getHeader().createEntry( PAYLOADFORMAT, "cpio");
    format.getHeader().createEntry( PAYLOADCOMPRESSOR, "gzip");

    addDependencyLess( "rpmlib(VersionedDependencies)", "3.0.3-1");
    addDependencyLess( "rpmlib(CompressedFileNames)", "3.0.4-1");
    addDependencyLess( "rpmlib(PayloadFilesHavePrefix)", "4.0-1");
  }
 
  public void addBuiltinDirectory(String builtinDirectory) {
    contents.addLocalBuiltinDirectory(builtinDirectory);
  }

  public void addObsoletes(final String name, final int comparison, final String version) {
    obsoletes.put(name, version);
    obsoletesFlags.put(name, comparison);
   
  }
 
  public void addObsoletesLess (final CharSequence name, final CharSequence version) {
    int flag = LESS | EQUAL;
    addObsoletes(name, version, flag);
  }
 
  public void addObsoletesMore (final CharSequence name, final CharSequence version) {
    int flag = GREATER | EQUAL;
    addObsoletes(name, version, flag);
  }
 
  protected void addObsoletes( final CharSequence name, final CharSequence version, final int flag) {
    obsoletes.put( name.toString(), version);
    obsoletesFlags.put( name.toString(), flag);
  }
 
  public void addConflicts(final String name, final int comparison, final String version) {
    conflicts.put(name, version);
    conflictsFlags.put(name, comparison);
  }
 
  public void addConflictsLess(final CharSequence name, final CharSequence version) {
    int flag = LESS | EQUAL;
    addConflicts(name, version, flag);
  }
 
  public void addConflictsMore(final CharSequence name, final CharSequence version) {
    int flag = GREATER | EQUAL;
    addConflicts(name, version, flag);
  }
 
  protected void addConflicts(final CharSequence name, final CharSequence version, final int flag) {
    conflicts.put(name.toString(), version);
    conflictsFlags.put(name.toString(), flag);
  }
 
  /**
   * Adds a dependency to the RPM package. This dependency version will be marked as the exact
   * requirement, and the package will require the named dependency with exactly this version at
   * install time.
   *
   * @param name the name of the dependency.
   * @param comparison the comparison flag.
   * @param version the version identifier.
   */
  public void addDependency( final String name, final int comparison, final String version ) {
    dependencies.put( name, version);
    flags.put( name, comparison);
  }

  /**
   * Adds a dependency to the RPM package. This dependency version will be marked as the maximum
   * allowed, and the package will require the named dependency with this version or lower at
   * install time.
   *
   * @param name the name of the dependency.
   * @param version the version identifier.
   */
  public void addDependencyLess( final CharSequence name, final CharSequence version) {
    int flag = LESS | EQUAL;
    if (name.toString().startsWith("rpmlib(")){
      flag = flag | RPMLIB;
    }
    addDependency( name, version, flag);
  }

  /**
   * Adds a dependency to the RPM package. This dependency version will be marked as the minimum
   * allowed, and the package will require the named dependency with this version or higher at
   * install time.
   *
   * @param name the name of the dependency.
   * @param version the version identifier.
   */
  public void addDependencyMore( final CharSequence name, final CharSequence version) {
    addDependency( name, version, GREATER | EQUAL);
  }

  /**
   * Adds a dependency to the RPM package. This dependency version will be marked as the exact
   * requirement, and the package will require the named dependency with exactly this version at
   * install time.
   *
   * @param name the name of the dependency.
   * @param version the version identifier.
   * @param flag the file flags
   */
  protected void addDependency( final CharSequence name, final CharSequence version, final int flag) {
    dependencies.put( name.toString(), version);
    flags.put( name.toString(), flag);
  }
 
  /**
     * Adds a header entry value to the header. For example use this to set the source RPM package
     * name on your RPM
     * @param tag the header tag to set
     * @param value the value to set the header entry with
     */
  public void addHeaderEntry( final Tag tag, final String value) {
      format.getHeader().createEntry(tag, value);
  }

  /**
   * @param illegalChars the illegal characters to check for.
   * @param variable the character sequence to check for illegal characters.
   * @param variableName the name to include in IllegalArgumentException
   * @throws IllegalArgumentException if passed in character sequence contains dashes.
   */
  private void checkVariableContainsIllegalChars(final char[] illegalChars, final CharSequence variable, final String variableName) {
    for (int i = 0; i < variable.length(); i++) {
      char currChar = variable.charAt(i);
      for (char illegalChar : illegalChars) {
        if (currChar == illegalChar) {
          throw new IllegalArgumentException(variableName + " with value: '" + variable + "' contains illegal character " + currChar);
        }
      }
    }
  }

  /**
   * <b>Required Field</b>. Sets the package information, such as the rpm name, the version, and the release number.
   *
   * @param name the name of the RPM package.
   * @param version the version of the new package.
   * @param release the release number, specified after the version, of the new RPM.
   * @throws IllegalArgumentException if version or release contain
   *         dashes, as they are explicitly disallowed by RPM file format.
   */
  public void setPackage( final CharSequence name, final CharSequence version, final CharSequence release) {
    checkVariableContainsIllegalChars(ILLEGAL_CHARS_NAME, name, "name");
    checkVariableContainsIllegalChars(ILLEGAL_CHARS_VARIABLE, version, "version");
    checkVariableContainsIllegalChars(ILLEGAL_CHARS_VARIABLE, release, "release");
    format.getLead().setName( name + "-" + version + "-" + release);
    format.getHeader().createEntry( NAME, name);
    format.getHeader().createEntry( VERSION, version);
    format.getHeader().createEntry( RELEASE, release);
    format.getHeader().createEntry( PROVIDENAME, new String[] { String.valueOf(name) });
    format.getHeader().createEntry( PROVIDEVERSION, 8, new String[] { "0:" + version + "-" + release});
    format.getHeader().createEntry( PROVIDEFLAGS, new int[] { 8});
  }
 
  /**
   * <b>Required Field</b>. Sets the type of the RPM to be either binary or source.
   *
   * @param type the type of RPM to generate.
   */
  public void setType( final RpmType type) {
    format.getLead().setType( type);
  }

  /**
   * <b>Required Field</b>. Sets the platform related headers for the resulting RPM. The platform is specified as a
   * combination of target architecture and OS.
   *
   * @param arch the target architecture.
   * @param os the target operating system.
   */
  public void setPlatform( final Architecture arch, final Os os) {
    format.getLead().setArch( arch);
    format.getLead().setOs( os);
   
    final CharSequence archName = arch.toString().toLowerCase();
    final CharSequence osName = os.toString().toLowerCase();
    format.getHeader().createEntry( ARCH, archName);
    format.getHeader().createEntry( OS, osName);
    format.getHeader().createEntry( PLATFORM, archName + "-" + osName);
    format.getHeader().createEntry( RHNPLATFORM, archName);
  }

    /**
     * <b>Required Field</b>. Sets the platform related headers for the resulting RPM. The platform is specified as a
     * combination of target architecture and OS.
     *
     * @param arch the target architecture.
     * @param osName the non-standard target operating system.
     */
    public void setPlatform( final Architecture arch, final CharSequence osName) {
            format.getLead().setArch( arch);
            format.getLead().setOs( Os.UNKNOWN);
           
            final CharSequence archName = arch.toString().toLowerCase();
            format.getHeader().createEntry( ARCH, archName);
            format.getHeader().createEntry( OS, osName);
            format.getHeader().createEntry( PLATFORM, archName + "-" + osName);
            format.getHeader().createEntry( RHNPLATFORM, archName);
    }
 
  /**
   * <b>Required Field</b>. Sets the summary text for the file. The summary is generally a short, one line description of the
   * function of the package, and is often shown by RPM tools.
   *
   * @param summary summary text.
   */
  public void setSummary( final CharSequence summary) {
    if ( summary != null) format.getHeader().createEntry( SUMMARY, summary);
  }

  /**
   * <b>Required Field</b>. Sets the description text for the file. The description is often a paragraph describing the
   * package in detail.
   *
   * @param description description text.
   */
  public void setDescription( final CharSequence description) {
    if ( description != null) format.getHeader().createEntry( DESCRIPTION, description);
  }

  /**
   * <b>Required Field</b>. Sets the build host for the RPM. This is an internal field.
   *
   * @param host hostname of the build machine.
   */
  public void setBuildHost( final CharSequence host) {
    if ( host != null) format.getHeader().createEntry( BUILDHOST, host);
  }

  /**
   * <b>Required Field</b>. Lists the license under which this software is distributed. This field may be
   * displayed by RPM tools.
   *
   * @param license the chosen distribution license.
   */
  public void setLicense( final CharSequence license) {
    if ( license != null) format.getHeader().createEntry( LICENSE, license);
  }

  /**
   * <b>Required Field</b>. Software group to which this package belongs.  The group describes what sort of
   * function the software package provides.
   *
   * @param group target group.
   */
  public void setGroup( final CharSequence group) {
    if ( group != null) format.getHeader().createEntry( GROUP, group);
  }

  /**
   * <b>Required Field</b>. Distribution tag listing the distributable package.
   *
   * @param distribution the distribution.
   */
  public void setDistribution( final CharSequence distribution) {
    if ( distribution != null) format.getHeader().createEntry( DISTRIBUTION, distribution);
  }
  /**
   * <b>Required Field</b>. Vendor tag listing the organization providing this software package.
   *
   * @param vendor software vendor.
   */
  public void setVendor( final CharSequence vendor) {
    if ( vendor != null) format.getHeader().createEntry( VENDOR, vendor);
  }

  /**
   * <b>Required Field</b>. Build packager, usually the username of the account building this RPM.
   *
   * @param packager packager name.
   */
  public void setPackager( final CharSequence packager) {
    if ( packager != null) format.getHeader().createEntry( PACKAGER, packager);
  }

  /**
   * <b>Required Field</b>. Website URL for this package, usually a project site.
   *
   * @param url the URL
   */
  public void setUrl( CharSequence url) {
    if ( url != null) format.getHeader().createEntry( URL, url);
  }

  /**
   * Declares a dependency that this package exports, and that other packages can use to
   * provide library functions.
   *
   * @param provides dependency provided by this package.
   */
  public void setProvides( final CharSequence provides) {
    if ( provides != null) format.getHeader().createEntry( PROVIDENAME, provides);
  }

  /**
   * Sets the group of contents to include in this RPM. Note that this method causes the existing
   * file set to be overwritten and therefore should be called before adding any other contents via
   * the <code>addFile()</code> methods.
   *
   * @param contents the set of contents to use in constructing this RPM.
   */
  public void setFiles( final Contents contents) {
    this.contents = contents;
  }
 
  /**
   * Adds a source rpm.
   *
   * @param rpm name of rpm source file
   */
  public void setSourceRpm( final String rpm) {
    if ( rpm != null) format.getHeader().createEntry( SOURCERPM, rpm);
  }
 
  /**
   * Sets the package prefix directories to allow any files installed under
   * them to be relocatable.
   *
   * @param prefixes Path prefixes which may be relocated
   */
  public void setPrefixes( final String... prefixes) {
    if ( prefixes != null) format.getHeader().createEntry( PREFIXES, prefixes);
  }

    /**
     * Return the content of the specified script file as a String.
     *
     * @param file the script file to be read
     */
    private String readScript( File file) throws IOException {
        if ( file == null) return null;

        StringBuilder script = new StringBuilder();
        BufferedReader in = new BufferedReader( new FileReader(file));

        try {
            String line;
            while (( line = in.readLine()) != null) {
                script.append( line);
                script.append( "\n");
            }
        } finally {
            in.close();
        }

        return script.toString();
    }

    /**
     * Returns the program use to run the specified script (guessed by parsing
     * the shebang at the beginning of the script)
     *
     * @param script
     */
    private String readProgram( String script) {
        String program = null;
       
        if ( script != null) {
            Pattern pattern = Pattern.compile( "^#!(/.*)");
            Matcher matcher = pattern.matcher( script);
            if ( matcher.find()) {
                program = matcher.group( 1);
            }           
        }
               
        return program;
    }

    /**
   * Declares a script to be run as part of the RPM pre-transaction. The
   * script will be run using the interpreter declared with the
   * {@link #setPreTransProgram(String)} method.
   *
   * @param script Script contents to run (i.e. shell commands)
   */
  public void setPreTransScript( final String script) {
    setPreTransProgram(readProgram(script));
    if ( script != null) format.getHeader().createEntry( PRETRANSSCRIPT, script);
  }

  /**
   * Declares a script file to be run as part of the RPM pre-transaction. The
   * script will be run using the interpreter declared with the
   * {@link #setPreTransProgram(String)} method.
   *
   * @param file Script to run (i.e. shell commands)
   * @throws IOException there was an IO error
   */
  public void setPreTransScript( final File file) throws IOException {
    setPreTransScript(readScript(file));
  }

  /**
   * Declares the interpreter to be used when invoking the RPM
   * pre-transaction script that can be set with the
   * {@link #setPreTransScript(String)} method.
   *
   * @param program Path to the interpreter
   */
  public void setPreTransProgram( final String program) {
    if ( null == program) {
      format.getHeader().createEntry( PRETRANSPROG, DEFAULTSCRIPTPROG);
    } else if ( 0 == program.length()){
      format.getHeader().createEntry( PRETRANSPROG, DEFAULTSCRIPTPROG);
    } else {
      format.getHeader().createEntry( PRETRANSPROG, program);
    }
  }
   
  /**
   * Declares a script to be run as part of the RPM pre-installation. The
   * script will be run using the interpreter declared with the
   * {@link #setPreInstallProgram(String)} method.
   *
   * @param script Script contents to run (i.e. shell commands)
   */
  public void setPreInstallScript( final String script) {
        setPreInstallProgram(readProgram(script));
        if ( script != null) format.getHeader().createEntry( PREINSCRIPT, script);
  }
 
  /**
   * Declares a script file to be run as part of the RPM pre-installation. The
   * script will be run using the interpreter declared with the
   * {@link #setPreInstallProgram(String)} method.
   *
   * @param file Script to run (i.e. shell commands)
   * @throws IOException there was an IO error
   */
  public void setPreInstallScript( final File file) throws IOException {
    setPreInstallScript(readScript(file));
  }

    /**
   * Declares the interpreter to be used when invoking the RPM
   * pre-installation script that can be set with the
   * {@link #setPreInstallScript(String)} method.
   *
   * @param program Path to the interpretter
   */
  public void setPreInstallProgram( final String program) {
    if ( null == program) {
      format.getHeader().createEntry( PREINPROG, DEFAULTSCRIPTPROG);
    } else if ( 0 == program.length()){
      format.getHeader().createEntry( PREINPROG, DEFAULTSCRIPTPROG);
    } else {
      format.getHeader().createEntry( PREINPROG, program);
    }
  }
 
  /**
   * Declares a script to be run as part of the RPM post-installation. The
   * script will be run using the interpreter declared with the
   * {@link #setPostInstallProgram(String)} method.
   *
   * @param script Script contents to run (i.e. shell commands)
   */
  public void setPostInstallScript( final String script) {
        setPostInstallProgram(readProgram(script));
        if ( script != null) format.getHeader().createEntry( POSTINSCRIPT, script);
  }
 
  /**
   * Declares a script file to be run as part of the RPM post-installation. The
   * script will be run using the interpreter declared with the
   * {@link #setPostInstallProgram(String)} method.
   *
   * @param file Script to run (i.e. shell commands)
   * @throws IOException there was an IO error
   */
  public void setPostInstallScript( final File file) throws IOException {
        setPostInstallScript(readScript(file));
  }

    /**
   * Declares the interpreter to be used when invoking the RPM
   * post-installation script that can be set with the
   * {@link #setPostInstallScript(String)} method.
   *
   * @param program Path to the interpreter
   */
  public void setPostInstallProgram( final String program) {
    if ( null == program) {
      format.getHeader().createEntry( POSTINPROG, DEFAULTSCRIPTPROG);
    } else if ( 0 == program.length()){
      format.getHeader().createEntry( POSTINPROG, DEFAULTSCRIPTPROG);
    } else {
      format.getHeader().createEntry( POSTINPROG, program);
    }
  }

  /**
   * Declares a script to be run as part of the RPM pre-uninstallation. The
   * script will be run using the interpreter declared with the
   * {@link #setPreUninstallProgram(String)} method.
   *
   * @param script Script contents to run (i.e. shell commands)
   */
  public void setPreUninstallScript( final String script) {
        setPreUninstallProgram(readProgram(script));
        if ( script != null) format.getHeader().createEntry( PREUNSCRIPT, script);
  }

  /**
   * Declares a script file to be run as part of the RPM pre-uninstallation. The
   * script will be run using the interpreter declared with the
   * {@link #setPreUninstallProgram(String)} method.
   *
   * @param file Script to run (i.e. shell commands)
   * @throws IOException there was an IO error
   */
  public void setPreUninstallScript( final File file) throws IOException {
        setPreUninstallScript(readScript(file));
  }

    /**
   * Declares the interpreter to be used when invoking the RPM
   * pre-uninstallation script that can be set with the
   * {@link #setPreUninstallScript(String)} method.
   *
   * @param program Path to the interpreter
   */
  public void setPreUninstallProgram( final String program) {
    if ( null == program) {
      format.getHeader().createEntry( PREUNPROG, DEFAULTSCRIPTPROG);
    } else if ( 0 == program.length()){
      format.getHeader().createEntry( PREUNPROG, DEFAULTSCRIPTPROG);
    } else {
      format.getHeader().createEntry( PREUNPROG, program);
    }
  }

  /**
   * Declares a script to be run as part of the RPM post-uninstallation. The
   * script will be run using the interpreter declared with the
   * {@link #setPostUninstallProgram(String)} method.
   *
   * @param script Script contents to run (i.e. shell commands)
   */
  public void setPostUninstallScript( final String script) {
        setPostUninstallProgram(readProgram(script));
        if ( script != null) format.getHeader().createEntry( POSTUNSCRIPT, script);
  }

  /**
   * Declares a script file to be run as part of the RPM post-uninstallation. The
   * script will be run using the interpreter declared with the
   * {@link #setPostUninstallProgram(String)} method.
   *
   * @param file Script contents to run (i.e. shell commands)
   * @throws IOException there was an IO error
   */
  public void setPostUninstallScript( final File file) throws IOException {
        setPostUninstallScript(readScript(file));
  }

    /**
   * Declares the interpreter to be used when invoking the RPM
   * post-uninstallation script that can be set with the
   * {@link #setPostUninstallScript(String)} method.
   *
   * @param program Path to the interpreter
   */
  public void setPostUninstallProgram( final String program) {
    if ( null == program) {
      format.getHeader().createEntry( POSTUNPROG, DEFAULTSCRIPTPROG);
    } else if ( 0 == program.length()){
      format.getHeader().createEntry( POSTUNPROG, DEFAULTSCRIPTPROG);
    } else {
      format.getHeader().createEntry( POSTUNPROG, program);
    }
  }

  /**
   * Declares a script to be run as part of the RPM post-transaction. The
   * script will be run using the interpreter declared with the
   * {@link #setPostTransProgram(String)} method.
   *
   * @param script Script contents to run (i.e. shell commands)
   */
  public void setPostTransScript( final String script) {
    setPostTransProgram(readProgram(script));
    if ( script != null) format.getHeader().createEntry( POSTTRANSSCRIPT, script);
  }

  /**
   * Declares a script file to be run as part of the RPM post-transaction. The
   * script will be run using the interpreter declared with the
   * {@link #setPostTransProgram(String)} method.
   *
   * @param file Script contents to run (i.e. shell commands)
   * @throws IOException there was an IO error
   */
  public void setPostTransScript( final File file) throws IOException {
    setPostTransScript(readScript(file));
  }

  /**
   * Declares the interpreter to be used when invoking the RPM
   * post-transaction script that can be set with the
   * {@link #setPostTransScript(String)} method.
   *
   * @param program Path to the interpreter
   */
  public void setPostTransProgram( final String program) {
    if ( null == program) {
      format.getHeader().createEntry( POSTTRANSPROG, DEFAULTSCRIPTPROG);
    } else if ( 0 == program.length()){
      format.getHeader().createEntry( POSTTRANSPROG, DEFAULTSCRIPTPROG);
    } else {
      format.getHeader().createEntry( POSTTRANSPROG, program);
    }
  }

  /**
   * Adds a trigger to the RPM package.
   *
   * @param script the script to add.
   * @param prog the interpreter with which to run the script.
   * @param depends the map of rpms and versions that will trigger the script
   * @param flag the trigger type (SCRIPT_TRIGGERPREIN, SCRIPT_TRIGGERIN, SCRIPT_TRIGGERUN, or SCRIPT_TRIGGERPOSTUN)
   * @throws IOException there was an IO error
   */
  public void addTrigger( final File script, final String prog, final Map< String, IntString> depends, final int flag) throws IOException {
    triggerscripts.add(readScript(script));
    if ( null == prog) {
      triggerscriptprogs.add(DEFAULTSCRIPTPROG);
    } else if ( 0 == prog.length()){
      triggerscriptprogs.add(DEFAULTSCRIPTPROG);
    } else {
      triggerscriptprogs.add(prog);
    }
    for ( Map.Entry< String, IntString> depend : depends.entrySet()) {
      triggernames.add( depend.getKey());
      triggerflags.add( depend.getValue().getInt() | flag);
      triggerversions.add( depend.getValue().getString());
      triggerindexes.add ( triggerCounter);
    }
    triggerCounter++;
  }

  /**
   * Add the specified file to the repository payload in order.
   * The required header entries will automatically be generated
   * to record the directory names and file names, as well as their
   * digests.
   *
   * @param path the absolute path at which this file will be installed.
   * @param source the file content to include in this rpm.
   * @param mode the mode of the target file in standard three octet notation
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addFile( final String path, final File source, final int mode) throws NoSuchAlgorithmException, IOException {
    contents.addFile( path, source, mode);
  }

  /**
   * Add the specified file to the repository payload in order.
   * The required header entries will automatically be generated
   * to record the directory names and file names, as well as their
   * digests.
   *
   * @param path the absolute path at which this file will be installed.
   * @param source the file content to include in this rpm.
   * @param mode the mode of the target file in standard three octet notation
   * @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addFile( final String path, final File source, final int mode, final int dirmode) throws NoSuchAlgorithmException, IOException {
    contents.addFile( path, source, mode, dirmode);
  }

  /**
     * Add the specified file to the repository payload in order.
     * The required header entries will automatically be generated
     * to record the directory names and file names, as well as their
     * digests.
     *
     * @param path the absolute path at which this file will be installed.
     * @param source the file content to include in this rpm.
     * @param mode the mode of the target file in standard three octet notation
     * @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
     * @param uname user owner for the given file
     * @param gname group owner for the given file
     * @throws NoSuchAlgorithmException the algorithm isn't supported
     * @throws IOException there was an IO error
     */
    public void addFile( final String path, final File source, final int mode, final int dirmode, final String uname, final String gname) throws NoSuchAlgorithmException, IOException {
        contents.addFile( path, source, mode, null, uname, gname, dirmode);
    }

    /**
     * Add the specified file to the repository payload in order.
     * The required header entries will automatically be generated
     * to record the directory names and file names, as well as their
     * digests.
     *
     * @param path the absolute path at which this file will be installed.
     * @param source the file content to include in this rpm.
     * @param mode the mode of the target file in standard three octet notation
     * @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
     * @param directive directive indicating special handling for this file.
     * @param uname user owner for the given file
     * @param gname group owner for the given file
     * @throws NoSuchAlgorithmException the algorithm isn't supported
     * @throws IOException there was an IO error
     */
    public void addFile( final String path, final File source, final int mode, final int dirmode, final Directive directive, final String uname, final String gname) throws NoSuchAlgorithmException, IOException {
        contents.addFile( path, source, mode, directive, uname, gname, dirmode);
    }

    /**
     * Add the specified file to the repository payload in order.
     * The required header entries will automatically be generated
     * to record the directory names and file names, as well as their
     * digests.
     *
     * @param path the absolute path at which this file will be installed.
     * @param source the file content to include in this rpm.
     * @param mode the mode of the target file in standard three octet notation, or -1 for default.
     * @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
     * @param directive directive indicating special handling for this file.
     * @param uname user owner for the given file, or null for default user.
     * @param gname group owner for the given file, or null for default group.
     * @param addParents whether to create parent directories for the file, defaults to true for other methods.
     * @throws NoSuchAlgorithmException the algorithm isn't supported
     * @throws IOException there was an IO error
     */
    public void addFile( final String path, final File source, final int mode, final int dirmode, final Directive directive, final String uname, final String gname, final boolean addParents) throws NoSuchAlgorithmException, IOException {
        contents.addFile( path, source, mode, directive, uname, gname, dirmode, addParents);
    }

  /**
   * Add the specified file to the repository payload in order.
   * The required header entries will automatically be generated
   * to record the directory names and file names, as well as their
   * digests.
   *
   * @param path the absolute path at which this file will be installed.
   * @param source the file content to include in this rpm.
   * @param mode the mode of the target file in standard three octet notation
   * @param directive directive indicating special handling for this file.
   * @param uname user owner for the given file
   * @param gname group owner for the given file
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addFile( final String path, final File source, final int mode, final Directive directive, final String uname, final String gname) throws NoSuchAlgorithmException, IOException {
    contents.addFile( path, source, mode, directive, uname, gname);
  }
 
  /**
   * Add the specified file to the repository payload in order.
   * The required header entries will automatically be generated
   * to record the directory names and file names, as well as their
   * digests.
   *
   * @param path the absolute path at which this file will be installed.
   * @param source the file content to include in this rpm.
   * @param mode the mode of the target file in standard three octet notation
   * @param directive directive indicating special handling for this file.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addFile( final String path, final File source, final int mode, final Directive directive) throws NoSuchAlgorithmException, IOException {
    contents.addFile( path, source, mode, directive);
  }

  /**
   * Adds the file to the repository with the default mode of <code>644</code>.
   *
   * @param path the absolute path at which this file will be installed.
   * @param source the file content to include in this rpm.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addFile( final String path, final File source) throws NoSuchAlgorithmException, IOException {
    contents.addFile( path, source);
  }

  /**
   * Add the specified file to the repository payload in order by URL.
   * The required header entries will automatically be generated
   * to record the directory names and file names, as well as their
   * digests.
   *
   * @param path the absolute path at which this file will be installed.
   * @param source the file content to include in this rpm.
   * @param mode the mode of the target file in standard three octet notation
   *  @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addURL( final String path, final URL source, final int mode, final int dirmode) throws NoSuchAlgorithmException, IOException {
    contents.addURL( path, source, mode, null, null, null, dirmode);
  }

  /**
     * Add the specified file to the repository payload in order by URL.
     * The required header entries will automatically be generated
     * to record the directory names and file names, as well as their
     * digests.
     *
     * @param path the absolute path at which this file will be installed.
     * @param source the file content to include in this rpm.
     * @param mode the mode of the target file in standard three octet notation
     * @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
     * @param username ownership of added file
     * @param group ownership of added file
     * @throws NoSuchAlgorithmException the algorithm isn't supported
     * @throws IOException there was an IO error
     */
    public void addURL( final String path, final URL source, final int mode, final int dirmode, final String username, final String group) throws NoSuchAlgorithmException, IOException {
        contents.addURL( path, source, mode, null, username, group, dirmode);
    }

    /**
     * Add the specified file to the repository payload in order by URL.
     * The required header entries will automatically be generated
     * to record the directory names and file names, as well as their
     * digests.
     *
     * @param path the absolute path at which this file will be installed.
     * @param source the file content to include in this rpm.
     * @param mode the mode of the target file in standard three octet notation
     * @param dirmode the mode of the parent directories in standard three octet notation, or -1 for default.
     * @param directive directive indicating special handling for this file.
     * @param username ownership of added file
     * @param group ownership of added file
     * @throws NoSuchAlgorithmException the algorithm isn't supported
     * @throws IOException there was an IO error
     */
    public void addURL( final String path, final URL source, final int mode, final int dirmode, final Directive directive, final String username, final String group) throws NoSuchAlgorithmException, IOException {
        contents.addURL( path, source, mode, directive, username, group, dirmode);
    }

  /**
   * Adds the directory to the repository with the default mode of <code>644</code>.
   *
   * @param path the absolute path to add as a directory.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addDirectory( final String path) throws NoSuchAlgorithmException, IOException {
    contents.addDirectory( path);
  }

  /**
   * Adds the directory to the repository.
   *
   * @param path the absolute path to add as a directory.
   * @param permissions the mode of the directory in standard three octet notation.
   * @param directive directive indicating special handling for this file.
   * @param uname user owner of the directory
   * @param gname group owner of the directory
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addDirectory( final String path, final int permissions, final Directive directive, final String uname, final String gname) throws NoSuchAlgorithmException, IOException {
    contents.addDirectory( path, permissions, directive, uname, gname);
  }
 
  /**
   * Adds the directory to the repository.
   *
   * @param path the absolute path to add as a directory.
   * @param permissions the mode of the directory in standard three octet notation.
   * @param directive directive indicating special handling for this file.
   * @param uname user owner of the directory
   * @param gname group owner of the directory
   * @param addParents whether to add parent directories to the rpm
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addDirectory( final String path, final int permissions, final Directive directive, final String uname, final String gname, final boolean addParents) throws NoSuchAlgorithmException, IOException {
    contents.addDirectory( path, permissions, directive, uname, gname, addParents);
  }
 
  /**
   * Adds the directory to the repository with the default mode of <code>644</code>.
   *
   * @param path the absolute path to add as a directory.
   * @param directive directive indicating special handling for this file.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addDirectory( final String path, final Directive directive) throws NoSuchAlgorithmException, IOException {
    contents.addDirectory( path, directive);
  }

  /**
   * Adds a symbolic link to the repository.
   *
   * @param path the absolute path at which this link will be installed.
   * @param target the path of the file this link will point to.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addLink( final String path, final String target) throws NoSuchAlgorithmException, IOException {
    contents.addLink( path, target);
  }

  /**
   * Adds a symbolic link to the repository.
   *
   * @param path the absolute path at which this link will be installed.
   * @param target the path of the file this link will point to.
   * @param permissions the permissions flags
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public void addLink( final String path, final String target, int permissions) throws NoSuchAlgorithmException, IOException {
    contents.addLink( path, target, permissions);
  }

  /**
   * Add a key to generate a new signature for the header and payload portions of the
   * rpm file. Supported algorithms are "MD5withRSA" and "SHAwithDSA".
   *
   * @param key private key to use in generating a signature.
   */
  public void addSignature( final PrivateKey key) {
    signatures.add( key);
  }

    /**
     * Sets the PGP key ring file used for header and header + payload signature.
     * Alternatively you can set the private key directly with {@link #setPrivateKey(org.bouncycastle.openpgp.PGPPrivateKey)}
     * @param privateKeyRingFile the private key ring file
     */
    public void setPrivateKeyRingFile( File privateKeyRingFile ) {
        this.privateKeyRingFile = privateKeyRingFile;
    }

    /**
     * Selects a private key from the current {@link #setPrivateKeyRingFile(java.io.File) private key ring file}.
     * If no key is specified, the first signing key will be selected.
     * @param privateKeyId hex key id
     */
    public void setPrivateKeyId( String privateKeyId ) {
        this.privateKeyId = privateKeyId;
    }

    /**
     * Passphrase for the private key
     * @param privateKeyPassphrase the private key pass phrase
     */
    public void setPrivateKeyPassphrase( String privateKeyPassphrase ) {
        this.privateKeyPassphrase = privateKeyPassphrase;
    }

    /**
     * Sets the private key for header and payload signing directly. Alternatively, you can set
     * {@link #setPrivateKeyRingFile(java.io.File) key ring file}, {@link #setPrivateKeyId(String) key id}
     * and {@link #setPrivateKeyPassphrase(String) passphrase} directly. Setting the private key has more
     * priorisation than providing key ring file.
     * @param privateKey the PGP private key
     */
    public void setPrivateKey( PGPPrivateKey privateKey ) {
        this.privateKey = privateKey;
    }

    /**
   * Generates an RPM with a standard name consisting of the RPM package name, version, release,
   * and type in the given directory.
   *
   * @param directory the destination directory for the new RPM file.
   * @return the name of the rpm
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  public String build( final File directory) throws NoSuchAlgorithmException, IOException {
    final String rpm = format.getLead().getName() + "." + format.getLead().getArch().toString().toLowerCase() + ".rpm";
    final File file = new File( directory, rpm);
    if ( file.exists()) file.delete();
    build( new RandomAccessFile( file, "rw").getChannel());
    return rpm;
  }

  /**
   * Generates the rpm file to the provided file channel. This file channel must support memory mapping
   * and therefore should be created from a {@link RandomAccessFile}, otherwise an {@link IOException} will be
   * generated.
   *
   * @param original the {@link FileChannel} to which the resulting RPM will be written.
   * @throws NoSuchAlgorithmException the algorithm isn't supported
   * @throws IOException there was an IO error
   */
  @SuppressWarnings( "unchecked")
  public void build( final FileChannel original) throws NoSuchAlgorithmException, IOException {
    final WritableChannelWrapper output = new WritableChannelWrapper( original);


    format.getHeader().createEntry( EPOCH, 0);
    format.getHeader().createEntry( REQUIRENAME, dependencies.keySet().toArray( new String[ dependencies.size()]));
    format.getHeader().createEntry( REQUIREVERSION, dependencies.values().toArray( new String[ dependencies.size()]));
    format.getHeader().createEntry( REQUIREFLAGS, convert( flags.values().toArray( new Integer[ flags.size()])));
   
    if (0 < obsoletes.size())
    {
      format.getHeader().createEntry( OBSOLETENAME, obsoletes.keySet().toArray(new String[ obsoletes.size() ]));
      format.getHeader().createEntry( OBSOLETEVERSION, obsoletes.values().toArray(new String[ obsoletes.size() ]));
      format.getHeader().createEntry( OBSOLETEFLAGS, convert(obsoletesFlags.values().toArray(new Integer[ obsoletesFlags.size() ])));
    }
   
    if (0 < conflicts.size())
    {
      format.getHeader().createEntry( CONFLICTNAME, conflicts.keySet().toArray(new String[ conflicts.size() ]));
      format.getHeader().createEntry(CONFLICTVERSION, conflicts.values().toArray(new String[ conflicts.size() ]));
      format.getHeader().createEntry( CONFLICTFLAGS, convert( conflictsFlags.values().toArray( new Integer [ conflictsFlags.size()])));
    }
   
    format.getHeader().createEntry( SIZE, contents.getTotalSize());
    format.getHeader().createEntry( DIRNAMES, contents.getDirNames());
    format.getHeader().createEntry( DIRINDEXES, contents.getDirIndexes());
    format.getHeader().createEntry( BASENAMES, contents.getBaseNames());
   

    if ( 0 < triggerCounter) {
      format.getHeader().createEntry( TRIGGERSCRIPTS, triggerscripts.toArray( new String[ triggerscripts.size()]));
      format.getHeader().createEntry( TRIGGERNAME, triggernames.toArray( new String[ triggernames.size()]));
      format.getHeader().createEntry( TRIGGERVERSION, triggerversions.toArray( new String[ triggerversions.size()]));
      format.getHeader().createEntry( TRIGGERFLAGS, convert( triggerflags.toArray( new Integer[ triggerflags.size()])));
      format.getHeader().createEntry( TRIGGERINDEX, convert( triggerindexes.toArray( new Integer[ triggerindexes.size()])));
      format.getHeader().createEntry( TRIGGERSCRIPTPROG, triggerscriptprogs.toArray( new String[ triggerscriptprogs.size()]));
    }

    format.getHeader().createEntry( FILEMD5S, contents.getMD5s());
    format.getHeader().createEntry( FILESIZES, contents.getSizes());
    format.getHeader().createEntry( FILEMODES, contents.getModes());
    format.getHeader().createEntry( FILERDEVS, contents.getRdevs());
    format.getHeader().createEntry( FILEMTIMES, contents.getMtimes());
    format.getHeader().createEntry( FILELINKTOS, contents.getLinkTos());
    format.getHeader().createEntry( FILEFLAGS, contents.getFlags());
    format.getHeader().createEntry( FILEUSERNAME, contents.getUsers());
    format.getHeader().createEntry( FILEGROUPNAME, contents.getGroups());
    format.getHeader().createEntry( FILEVERIFYFLAGS, contents.getVerifyFlags());
    format.getHeader().createEntry( FILEDEVICES, contents.getDevices());
    format.getHeader().createEntry( FILEINODES, contents.getInodes());
    format.getHeader().createEntry( FILELANGS, contents.getLangs());
    format.getHeader().createEntry( FILECONTEXTS, contents.getContexts());

    format.getHeader().createEntry( PAYLOADFLAGS, new String[] { "9"});

    final Entry< int[]> sigsize = ( Entry< int[]>) format.getSignature().addEntry( LEGACY_SIGSIZE, 1);
    final Entry< int[]> payload = ( Entry< int[]>) format.getSignature().addEntry( PAYLOADSIZE, 1);
    final Entry< byte[]> md5 = ( Entry< byte[]>) format.getSignature().addEntry( LEGACY_MD5, 16);
    final Entry< String[]> sha = ( Entry< String[]>) format.getSignature().addEntry( SHA1HEADER, 1);
    sha.setSize( SHASIZE);

        SignatureGenerator signatureGenerator = createSignatureGenerator();
        signatureGenerator.prepare( format.getSignature() );

    format.getLead().write( original);
    signature.setValues( getSignature( format.getSignature().count()));
    Util.empty( output, ByteBuffer.allocate( format.getSignature().write( original)));

    final Key< Integer> sigsizekey = output.start();
    final Key< byte[]> shakey = output.start( "SHA");
    final Key< byte[]> md5key = output.start( "MD5");
        signatureGenerator.startBeforeHeader( output );

    immutable.setValues( getImmutable( format.getHeader().count()));
    format.getHeader().write( output);
    sha.setValues( new String[] { Util.hex( output.finish( shakey))});
        signatureGenerator.finishAfterHeader( output );

    final GZIPOutputStream zip = new GZIPOutputStream( Channels.newOutputStream( output));
    final WritableChannelWrapper compressor = new WritableChannelWrapper( Channels.newChannel( zip));
    final Key< Integer> payloadkey = compressor.start();

    int total = 0;
    final ByteBuffer buffer = ByteBuffer.allocate( 4096);
    for ( CpioHeader header : contents.headers()) {
      if ( ( header.getFlags() & Directive.RPMFILE_GHOST ) == Directive.RPMFILE_GHOST ) {
        continue;
      }
      final String path = header.getName();
      if ( path.startsWith( "/")) header.setName( "." + path);
      total = header.write( compressor, total);
     
      final Object object = contents.getSource( header);
      if ( object instanceof File) {
        final FileChannel in = new FileInputStream(( File) object).getChannel();
        while ( in.read(( ByteBuffer) buffer.rewind()) > 0) {
          total += compressor.write(( ByteBuffer) buffer.flip());
          buffer.compact();
        }
        total += header.skip( compressor, total);
        in.close();
      } else if ( object instanceof URL) {
        final ReadableByteChannel in = Channels.newChannel((( URL) object).openConnection().getInputStream());
        while ( in.read(( ByteBuffer) buffer.rewind()) > 0) {
          total += compressor.write(( ByteBuffer) buffer.flip());
          buffer.compact();
        }
        total += header.skip( compressor, total);
        in.close();
      } else if ( object instanceof CharSequence) {
        final CharSequence target = ( CharSequence) object;
        total += compressor.write( ByteBuffer.wrap( String.valueOf( target).getBytes()));
        total += header.skip( compressor, target.length());
      }
    }
   
    final CpioHeader trailer = new CpioHeader();
    trailer.setLast();
    total = trailer.write( compressor, total);
    trailer.skip( compressor, total);

    int length = compressor.finish( payloadkey);
    int pad = Util.difference( length, 3);
    Util.empty( compressor, ByteBuffer.allocate( pad));
    length += pad;

    payload.setValues( new int[] { length});
    zip.finish();
   
    md5.setValues( output.finish( md5key));
    sigsize.setValues( new int[] { output.finish( sigsizekey)});
        signatureGenerator.finishAfterPayload( output );
    format.getSignature().writePending( original);
    output.close();
  }

    protected SignatureGenerator createSignatureGenerator() {
        if (privateKey != null) {
           return new SignatureGenerator( privateKey );
        }
        return new SignatureGenerator( privateKeyRingFile, privateKeyId, privateKeyPassphrase);
    }

    protected byte[] getSignature( final int count) {
    return getSpecial( 0x0000003E, count);
  }

  protected byte[] getImmutable( final int count) {
    return getSpecial( 0x0000003F, count);
  }

  /**
   * Returns the special header expected by RPM for
   * a particular header.
   * @param tag the tag to get
   * @param count the number to get
   * @return the header bytes
   */
  protected byte[] getSpecial( final int tag, final int count) {
    final ByteBuffer buffer = ByteBuffer.allocate( 16);
    buffer.putInt( tag);
    buffer.putInt( 0x00000007);
    buffer.putInt( count * -16);
    buffer.putInt( 0x00000010);
    return buffer.array();
  }

  /**
   * Converts an array of Integer objects into an equivalent
   * array of int primitives.
   * @param ints the array of Integer objects
   * @return the primitive ints array
   */
  protected int[] convert( final Integer[] ints) {
    int[] array = new int[ ints.length];
    int count = 0;
    for ( int i : ints) array[ count++] = i;
    return array;
  }
}
TOP

Related Classes of org.redline_rpm.Builder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.