checksums = Collections.singletonMap(HashAlgorithm.SHA1, sha1);
- repoContext.addPackage ( file, info, checksums, HashAlgorithm.SHA1 );
+ final String location = String.format("pool/%s/%s", art.getId(), art.getName());
+ final RepositoryCreator.FileInformation file = new RepositoryCreator.FileInformation(art.getCreationInstant(), art.getSize(), location);
+
+ repoContext.addPackage(file, info, checksums, HashAlgorithm.SHA1);
+ }
}
} );
@@ -134,4 +140,4 @@ private DefaultXmlContext makeXmlContext ()
final DefaultXmlContext xmlCtx = new DefaultXmlContext ( dbf, this.xml.newTransformerFactory () );
return xmlCtx;
}
-}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.packagedrone.repo.adapter.rpm/src/org/eclipse/packagedrone/repo/adapter/rpm/yum/internal/YumRecipe.java b/bundles/org.eclipse.packagedrone.repo.adapter.rpm/src/org/eclipse/packagedrone/repo/adapter/rpm/yum/internal/YumRecipe.java
index 4f071f81..454c3ca8 100644
--- a/bundles/org.eclipse.packagedrone.repo.adapter.rpm/src/org/eclipse/packagedrone/repo/adapter/rpm/yum/internal/YumRecipe.java
+++ b/bundles/org.eclipse.packagedrone.repo.adapter.rpm/src/org/eclipse/packagedrone/repo/adapter/rpm/yum/internal/YumRecipe.java
@@ -19,7 +19,7 @@ public class YumRecipe implements Recipe
@Override
public LinkTarget setup ( final String channelId, final AspectableChannel channel )
{
- channel.addAspects ( true, "rpm", "yum" );
+ channel.addAspects ( true, "rpm", "yum", "rpm.signer" );
return null;
}
}
diff --git a/bundles/org.eclipse.packagedrone.repo.channel.apm/src/org/eclipse/packagedrone/repo/channel/apm/ModifyContextImpl.java b/bundles/org.eclipse.packagedrone.repo.channel.apm/src/org/eclipse/packagedrone/repo/channel/apm/ModifyContextImpl.java
index 2ccd8098..5c5a599f 100644
--- a/bundles/org.eclipse.packagedrone.repo.channel.apm/src/org/eclipse/packagedrone/repo/channel/apm/ModifyContextImpl.java
+++ b/bundles/org.eclipse.packagedrone.repo.channel.apm/src/org/eclipse/packagedrone/repo/channel/apm/ModifyContextImpl.java
@@ -7,6 +7,7 @@
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
+ * Walker Funk - Trident Systems Inc. - added system call to clear function to delete empty directories
*******************************************************************************/
package org.eclipse.packagedrone.repo.channel.apm;
@@ -30,6 +31,7 @@
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import java.lang.Runtime;
import org.eclipse.packagedrone.repo.MetaKey;
import org.eclipse.packagedrone.repo.channel.ArtifactInformation;
@@ -822,6 +824,16 @@ public void clear ()
this.state.setNumberOfArtifacts ( 0L );
this.state.setNumberOfBytes ( 0L );
+
+ final String cmd = "rm -rf " + System.getProperty ( "drone.storage.base" ) + "/channels/" + this.channelId + "/blobs/data/*";
+ try
+ {
+ Process p = Runtime.getRuntime().exec( new String[] {"sh", "-c", cmd} );
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException ( "Could not remove channel " + this.channelId + " data directories" );
+ }
}
@Override
diff --git a/bundles/org.eclipse.packagedrone.repo.channel.impl/src/org/eclipse/packagedrone/repo/channel/impl/ChannelServiceImpl.java b/bundles/org.eclipse.packagedrone.repo.channel.impl/src/org/eclipse/packagedrone/repo/channel/impl/ChannelServiceImpl.java
index 19c2d9d1..bdff4d3b 100644
--- a/bundles/org.eclipse.packagedrone.repo.channel.impl/src/org/eclipse/packagedrone/repo/channel/impl/ChannelServiceImpl.java
+++ b/bundles/org.eclipse.packagedrone.repo.channel.impl/src/org/eclipse/packagedrone/repo/channel/impl/ChannelServiceImpl.java
@@ -7,6 +7,7 @@
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
+ * Walker Funk - Trident Systems Inc. - added system call to delete function to delete empty directories
*******************************************************************************/
package org.eclipse.packagedrone.repo.channel.impl;
@@ -28,6 +29,10 @@
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.stream.Collectors;
+import java.lang.Runtime;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
import org.eclipse.packagedrone.repo.MetaKey;
import org.eclipse.packagedrone.repo.aspect.ChannelAspectProcessor;
@@ -494,6 +499,17 @@ public boolean delete ( final By by )
channel.get ().delete ();
+ final String cmd = "rm -rf " + System.getProperty ( "drone.storage.base" ) + "/channels/" + instance.getChannelId ();
+
+ try
+ {
+ Process p = Runtime.getRuntime().exec(cmd);
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException ( "Could not remove channel " + instance.getChannelId () + " files/directories" );
+ }
+
return true;
}
}
diff --git a/bundles/org.eclipse.packagedrone.repo.signing.pgp/META-INF/MANIFEST.MF b/bundles/org.eclipse.packagedrone.repo.signing.pgp/META-INF/MANIFEST.MF
index 028ed19a..dbafd9bb 100644
--- a/bundles/org.eclipse.packagedrone.repo.signing.pgp/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.packagedrone.repo.signing.pgp/META-INF/MANIFEST.MF
@@ -18,6 +18,10 @@ Import-Package: com.google.gson;version="2.3.1",
org.eclipse.packagedrone.storage.apm;version="1.0.0",
org.eclipse.packagedrone.storage.apm.util;version="1.0.0",
org.eclipse.packagedrone.utils;version="1.0.0",
+ org.eclipse.packagedrone.utils.rpm;version="0.15.0",
+ org.eclipse.packagedrone.utils.rpm.build;version="0.15.0",
+ org.eclipse.packagedrone.utils.rpm.parse;version="0.15.0",
+ org.eclipse.packagedrone.utils.rpm.signature;version="0.15.0",
org.eclipse.packagedrone.utils.security.pgp;version="0.14.0",
org.eclipse.scada.utils;version="0.2.0",
org.osgi.framework;version="1.8.0",
diff --git a/bundles/org.eclipse.packagedrone.repo.signing.pgp/src/org/eclipse/packagedrone/repo/signing/pgp/internal/AbstractSecretKeySigningService.java b/bundles/org.eclipse.packagedrone.repo.signing.pgp/src/org/eclipse/packagedrone/repo/signing/pgp/internal/AbstractSecretKeySigningService.java
index 4fb3d506..58527d1f 100644
--- a/bundles/org.eclipse.packagedrone.repo.signing.pgp/src/org/eclipse/packagedrone/repo/signing/pgp/internal/AbstractSecretKeySigningService.java
+++ b/bundles/org.eclipse.packagedrone.repo.signing.pgp/src/org/eclipse/packagedrone/repo/signing/pgp/internal/AbstractSecretKeySigningService.java
@@ -7,6 +7,7 @@
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
+ * Walker Funk - Trident Systems Inc. - rpm signing components
*******************************************************************************/
package org.eclipse.packagedrone.repo.signing.pgp.internal;
@@ -16,6 +17,7 @@
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
@@ -32,6 +34,12 @@
import org.eclipse.packagedrone.VersionInformation;
import org.eclipse.packagedrone.repo.signing.SigningService;
import org.eclipse.packagedrone.utils.security.pgp.SigningStream;
+import org.eclipse.packagedrone.utils.rpm.signature.RsaHeaderSignatureProcessor;
+import org.eclipse.packagedrone.utils.rpm.signature.PgpHeaderSignatureProcessor;
+import org.eclipse.packagedrone.utils.rpm.signature.SignatureProcessors;
+import org.eclipse.packagedrone.utils.rpm.build.RpmRebuilder;
+import org.eclipse.packagedrone.utils.rpm.HashAlgorithm;
+import org.eclipse.packagedrone.utils.rpm.parse.RpmParserStream;
public abstract class AbstractSecretKeySigningService implements SigningService
{
@@ -125,6 +133,17 @@ public void sign ( final InputStream in, final OutputStream out, final boolean i
armoredOutput.close ();
}
+ @Override
+ public void signRpm ( Path path, RpmParserStream in ) throws Exception
+ {
+ RsaHeaderSignatureProcessor rhsp = new RsaHeaderSignatureProcessor ( this.privateKey, HashAlgorithm.SHA256 );
+ RpmRebuilder rpmRebuilder = new RpmRebuilder ( path, in.getLead (), in.getSignatureHeader (), in.getPayloadHeader () );
+ rpmRebuilder.setPayload ( in.getPayloadStreamer () );
+ rpmRebuilder.addSignatureProcessor ( rhsp );
+ in.close ();
+ rpmRebuilder.close ();
+ }
+
private static String trimTrailing ( final String line )
{
final char[] content = line.toCharArray ();
diff --git a/bundles/org.eclipse.packagedrone.repo.signing/META-INF/MANIFEST.MF b/bundles/org.eclipse.packagedrone.repo.signing/META-INF/MANIFEST.MF
index ef75b0b4..9aef462f 100644
--- a/bundles/org.eclipse.packagedrone.repo.signing/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.packagedrone.repo.signing/META-INF/MANIFEST.MF
@@ -5,4 +5,5 @@ Bundle-SymbolicName: org.eclipse.packagedrone.repo.signing
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: Jens Reimann
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Import-Package: org.eclipse.packagedrone.utils.rpm.parse;version="0.15.0"
Export-Package: org.eclipse.packagedrone.repo.signing;version="1.0.0"
diff --git a/bundles/org.eclipse.packagedrone.repo.signing/src/org/eclipse/packagedrone/repo/signing/SigningService.java b/bundles/org.eclipse.packagedrone.repo.signing/src/org/eclipse/packagedrone/repo/signing/SigningService.java
index 8937f7dd..2c5a48ee 100644
--- a/bundles/org.eclipse.packagedrone.repo.signing/src/org/eclipse/packagedrone/repo/signing/SigningService.java
+++ b/bundles/org.eclipse.packagedrone.repo.signing/src/org/eclipse/packagedrone/repo/signing/SigningService.java
@@ -7,17 +7,22 @@
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
+ * Walker Funk - Trident Systems Inc. - rpm signing components
*******************************************************************************/
package org.eclipse.packagedrone.repo.signing;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.Path;
+import org.eclipse.packagedrone.utils.rpm.parse.RpmParserStream;
public interface SigningService
{
public void sign ( InputStream in, OutputStream out, boolean inline ) throws Exception;
+ public void signRpm ( Path path, RpmParserStream in ) throws Exception;
+
public void printPublicKey ( OutputStream out ) throws IOException;
public OutputStream signingStream ( OutputStream out, boolean inline );
diff --git a/bundles/org.eclipse.packagedrone.utils.rpm/META-INF/MANIFEST.MF b/bundles/org.eclipse.packagedrone.utils.rpm/META-INF/MANIFEST.MF
index 2d1ef5e1..37cd51af 100644
--- a/bundles/org.eclipse.packagedrone.utils.rpm/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.packagedrone.utils.rpm/META-INF/MANIFEST.MF
@@ -27,7 +27,9 @@ Import-Package: com.google.common.io;version="18.0.0",
org.apache.commons.compress.utils;version="1.18.0",
org.bouncycastle.bcpg;version="1.52.0",
org.bouncycastle.openpgp;version="1.52.0",
+ org.bouncycastle.openpgp.operator;version="1.52.0",
org.bouncycastle.openpgp.operator.bc;version="1.52.0",
+ org.eclipse.packagedrone;version="1.0.0",
org.eclipse.packagedrone.utils.io;version="1.0.0",
org.eclipse.packagedrone.utils.security.pgp;version="0.14.0",
org.slf4j;version="1.7.2",
diff --git a/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/build/PayloadStreamer.java b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/build/PayloadStreamer.java
new file mode 100644
index 00000000..749512b6
--- /dev/null
+++ b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/build/PayloadStreamer.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Trident Systems, Inc.
+ * This software was developed with U.S government funding in support of the above
+ * contract. Trident grants unlimited rights to modify, distribute and incorporate
+ * our contributions to Eclipse Package Drone bound by the overall restrictions from
+ * the parent Eclipse Public License v1.0 available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Walker Funk - Trident Systems Inc. - initial implementation
+ *******************************************************************************/
+package org.eclipse.packagedrone.utils.rpm.build;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CountingOutputStream;
+
+public class PayloadStreamer implements AutoCloseable
+{
+ private final boolean autoFinish;
+
+ private final Path tempFile;
+
+ private final CountingOutputStream payloadCounter;
+
+ private OutputStream fileStream;
+
+ private boolean finished;
+
+ private boolean closed;
+
+ public PayloadStreamer ( final boolean autoFinish, DataInputStream in ) throws IOException
+ {
+ this.autoFinish = autoFinish;
+
+ this.tempFile = Files.createTempFile ( "rpm-", null );
+
+ try
+ {
+ this.fileStream = new BufferedOutputStream ( Files.newOutputStream ( this.tempFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING ) );
+
+ this.payloadCounter = new CountingOutputStream ( this.fileStream );
+
+ ByteStreams.copy( in, payloadCounter );
+ }
+ catch ( final IOException e )
+ {
+ Files.deleteIfExists ( this.tempFile );
+ throw e;
+ }
+ }
+
+ /**
+ * Stop streaming payload data
+ *
+ * If the streamer is already finished then nothing will happen
+ *
+ *
+ * @throws IOException
+ * in case of IO errors
+ */
+ public void finish () throws IOException
+ {
+ if ( this.finished )
+ {
+ return;
+ }
+
+ this.finished = true;
+
+ this.payloadCounter.close ();
+ }
+
+ public long getPayloadSize () throws IOException
+ {
+ checkFinished ( true );
+
+ return this.payloadCounter.getCount ();
+ }
+
+ public FileChannel openChannel () throws IOException
+ {
+ checkFinished ( true );
+
+ return FileChannel.open ( this.tempFile, StandardOpenOption.READ );
+ }
+
+ private void checkFinished ( final boolean allowAutoFinish ) throws IOException
+ {
+ if ( !this.finished && this.autoFinish && allowAutoFinish )
+ {
+ finish ();
+ }
+
+ if ( !this.finished )
+ {
+ throw new IllegalStateException ( "Recoderd has to be finished before accessing payload information or data" );
+ }
+ if ( this.closed )
+ {
+ throw new IllegalStateException ( "Recorder is already closed" );
+ }
+ }
+
+ @Override
+ public void close () throws IOException
+ {
+ this.closed = true;
+
+ try
+ {
+ // simply close the file stream
+
+ if ( this.fileStream != null )
+ {
+ this.fileStream.close ();
+ }
+ }
+ finally
+ {
+ // and delete the temp file
+
+ Files.deleteIfExists ( this.tempFile );
+ }
+ }
+}
diff --git a/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/build/RpmRebuilder.java b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/build/RpmRebuilder.java
new file mode 100644
index 00000000..22c6ad9c
--- /dev/null
+++ b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/build/RpmRebuilder.java
@@ -0,0 +1,363 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Trident Systems, Inc.
+ * This software was developed with U.S government funding in support of the above
+ * contract. Trident grants unlimited rights to modify, distribute and incorporate
+ * our contributions to Eclipse Package Drone bound by the overall restrictions from
+ * the parent Eclipse Public License v1.0 available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Walker Funk - Trident Systems Inc. - initial implementation
+ *******************************************************************************/
+package org.eclipse.packagedrone.utils.rpm.build;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.packagedrone.utils.rpm.RpmLead;
+import org.eclipse.packagedrone.utils.rpm.RpmSignatureTag;
+import org.eclipse.packagedrone.utils.rpm.RpmTag;
+import org.eclipse.packagedrone.utils.rpm.Rpms;
+import org.eclipse.packagedrone.utils.rpm.header.Header;
+import org.eclipse.packagedrone.utils.rpm.header.Headers;
+import org.eclipse.packagedrone.utils.rpm.signature.SignatureProcessor;
+import com.google.common.io.ByteStreams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A low level RPM file rebuilder
+ *
+ * This class handles the reconstructing of RPM files. It does not really care about the
+ * contents it writes but the content and the format of the content should come
+ * from a pre-parsed rpm (ideally via the RpmParserStreamer class) and
+ * added signature processor(s)
+ *
+ */
+public class RpmRebuilder implements AutoCloseable
+{
+ private static final OpenOption[] DEFAULT_OPEN_OPTIONS = new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING };
+
+ private final static Logger logger = LoggerFactory.getLogger ( RpmRebuilder.class );
+
+ private final FileChannel file;
+
+ private final RpmLead lead;
+
+ private final Header signatureHeader;
+
+ private final ByteBuffer header;
+
+ private boolean finished;
+
+ private PayloadStreamer payloadStreamer;
+
+ private final List signatureProcessors = new LinkedList<> ();
+
+ public RpmRebuilder ( final Path path, final RpmLead lead, final Header signatureHeader, final Header header ) throws IOException
+ {
+ requireNonNull ( path );
+ requireNonNull ( lead );
+ requireNonNull ( header );
+ requireNonNull ( signatureHeader );
+
+ this.file = FileChannel.open ( path, DEFAULT_OPEN_OPTIONS );
+ this.lead = lead;
+ this.signatureHeader = signatureHeader;
+
+ this.header = Headers.render ( header.makeEntries (), true, Rpms.IMMUTABLE_TAG_HEADER );
+ }
+
+ public void addSignatureProcessor ( final SignatureProcessor processor )
+ {
+ this.signatureProcessors.add ( processor );
+ }
+
+ public void addAllSignatureProcessors ( final List signatureProcessors )
+ {
+ this.signatureProcessors.addAll ( signatureProcessors );
+ }
+
+ public void setPayload ( final PayloadStreamer payloadStreamer ) throws IOException
+ {
+ checkNotFinished ();
+
+ requireNonNull ( payloadStreamer );
+
+ this.payloadStreamer = payloadStreamer;
+ }
+
+ private void checkNotFinished ()
+ {
+ if ( this.finished )
+ {
+ throw new IllegalStateException ( "Writing of RPM is already finished" );
+ }
+ }
+
+ private static void debug ( final String fmt, final Object... args )
+ {
+ logger.debug ( String.format ( fmt, args ) );
+ }
+
+ private void writeLead () throws IOException
+ {
+ // write lead
+
+ final ByteBuffer lead = ByteBuffer.allocate ( Rpms.LEAD_MAGIC.length + 2 + 4 + 66 + 2 + 2 + 16 );
+
+ lead.put ( Rpms.LEAD_MAGIC );
+ lead.put ( this.lead.getMajor () );
+ lead.put ( this.lead.getMinor () );
+
+ // 2 bytes type
+
+ lead.putShort ( this.lead.getType () );
+
+ // 2 bytes arch
+
+ lead.putShort ( this.lead.getArchitecture () );
+
+ // write package name
+
+ {
+ final ByteBuffer nameData = StandardCharsets.UTF_8.encode ( this.lead.getName () );
+ final int len = nameData.remaining ();
+ if ( len > 66 )
+ {
+ throw new IOException ( "Name exceeds 66 bytes" );
+ }
+
+ lead.put ( nameData );
+ if ( len < 66 )
+ {
+ // fill up
+ lead.put ( Rpms.EMPTY_128, 0, 66 - len );
+ }
+ }
+
+ // 2 bytes OS
+
+ lead.putShort ( this.lead.getOperatingSystem () );
+
+ // 2 bytes signature
+
+ lead.putShort ( (short)this.lead.getSignatureVersion () );
+
+ // 16 bytes reserved
+
+ lead.put ( Rpms.EMPTY_128, 0, 16 );
+
+ lead.flip ();
+ safeWrite ( lead );
+ }
+
+ private void safeWrite ( final ByteBuffer data ) throws IOException
+ {
+ while ( data.hasRemaining () )
+ {
+ this.file.write ( data );
+ }
+ }
+
+ private void writeSignatureHeader ( final Header> header ) throws IOException
+ {
+ // render header
+
+ final ByteBuffer buffer = Headers.render ( header.makeEntries (), true, Rpms.IMMUTABLE_TAG_SIGNATURE );
+
+ final int payloadSize = buffer.remaining ();
+
+ // header
+
+ debug ( "start header - offset: %s, len: %s", this.file.position (), payloadSize );
+ safeWrite ( buffer );
+
+ // padding
+
+ final int padding = Rpms.padding ( payloadSize );
+
+ if ( padding > 0 )
+ {
+ safeWrite ( ByteBuffer.wrap ( Rpms.EMPTY_128, 0, padding ) );
+ debug ( "write - padding - %s", padding );
+ }
+ }
+
+ @Override
+ public void close () throws IOException
+ {
+ try
+ {
+ finish ();
+ }
+ finally
+ {
+ this.file.close ();
+ this.payloadStreamer.close ();
+ }
+ }
+
+ private void finish () throws IOException
+ {
+ if ( this.finished )
+ {
+ return;
+ }
+
+ if ( this.payloadStreamer == null )
+ {
+ throw new IOException ( "Unable to finish RPM file, payload provider not set" );
+ }
+
+ this.finished = true;
+
+ final int headerSize = this.header.remaining ();
+ final long payloadSize = this.payloadStreamer.getPayloadSize ();
+ debug ( "data - %s - %s", headerSize, payloadSize );
+
+ // process signatures
+
+ processSignatures ( this.signatureHeader );
+
+ // write lead
+
+ writeLead ();
+
+ // write signature header
+
+ writeSignatureHeader ( this.signatureHeader );
+
+ // write the header
+
+ debug ( "package - offset: %s", this.file.position () );
+ safeWrite ( this.header.slice () ); // write sliced to keep the original position
+
+ debug ( "payload - offset: %s", this.file.position () );
+
+ // now append payload data
+
+ try ( ReadableByteChannel payloadChannel = this.payloadStreamer.openChannel () )
+ {
+ if ( payloadChannel instanceof FileChannel && !isForceCopy () )
+ {
+ final long count = copyFileChannel ( (FileChannel)payloadChannel, this.file );
+ debug ( "transfered - %s", count );
+ }
+ else
+ {
+ final long count = ByteStreams.copy ( payloadChannel, this.file );
+ debug ( "copyied - %s", count );
+ }
+ }
+
+ debug ( "end - offset: %s", this.file.position () );
+ }
+
+ /**
+ * Check of in-JVM copy should be forced over
+ * {@link FileChannel#transferTo(long, long, java.nio.channels.WritableByteChannel)}
+ *
+ * @return {@code true} if copying should be forced, {@code false}
+ * otherwise. Defaults to {@code false}.
+ */
+ private static boolean isForceCopy ()
+ {
+ return Boolean.getBoolean ( "org.eclipse.packagedrone.utils.rpm.build.RpmRebuilder.forceCopy" );
+ }
+
+ private static long copyFileChannel ( final FileChannel fileChannel, final FileChannel file ) throws IOException
+ {
+ long remaning = fileChannel.size ();
+ long position = 0;
+
+ while ( remaning > 0 )
+ {
+ // transfer next chunk
+
+ final long rc = fileChannel.transferTo ( position, remaning, file );
+
+ // check for negative result
+
+ if ( rc < 0 )
+ {
+ throw new IOException ( String.format ( "Failed to transfer bytes: rc = %s", rc ) );
+ }
+
+ debug ( "transferTo - position: %s, size: %s => rc: %s", position, remaning, rc );
+
+ // we should never get zero back, but check anyway
+
+ if ( rc == 0 )
+ {
+ break;
+ }
+
+ // update state
+
+ position += rc;
+ remaning -= rc;
+ }
+
+ // final check if we got it all
+
+ if ( remaning > 0 )
+ {
+ throw new IOException ( "Failed to transfer full content" );
+ }
+
+ return position;
+ }
+
+ private void processSignatures ( final Header signature ) throws IOException
+ {
+ // init
+
+ // for ( final SignatureProcessor processor : this.signatureProcessors )
+ // {
+ // processor.init ( this.payloadStreamer.getArchiveSize () );
+ // }
+
+ // feed the header
+
+ for ( final SignatureProcessor processor : this.signatureProcessors )
+ {
+ processor.feedHeader ( this.header.slice () );
+ }
+
+ // feed payload data
+
+ try ( ReadableByteChannel channel = this.payloadStreamer.openChannel () )
+ {
+ final ByteBuffer buf = ByteBuffer.wrap ( new byte[4096] );
+
+ while ( channel.read ( buf ) >= 0 )
+ {
+ buf.flip ();
+ for ( final SignatureProcessor processor : this.signatureProcessors )
+ {
+ processor.feedPayloadData ( buf.slice () );
+ }
+ buf.clear ();
+ }
+ }
+
+ // finish up
+
+ for ( final SignatureProcessor processor : this.signatureProcessors )
+ {
+ processor.finish ( signature );
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/info/RpmInformations.java b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/info/RpmInformations.java
index 2cb142f8..59de29bf 100644
--- a/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/info/RpmInformations.java
+++ b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/info/RpmInformations.java
@@ -7,10 +7,12 @@
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
+ * Walker Funk - Trident Systems Inc. - asArmored method for armoring signature data
*******************************************************************************/
package org.eclipse.packagedrone.utils.rpm.info;
import java.io.IOException;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -20,11 +22,14 @@
import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry;
import org.apache.commons.compress.archivers.cpio.CpioArchiveInputStream;
+import org.eclipse.packagedrone.VersionInformation;
import org.eclipse.packagedrone.utils.rpm.RpmSignatureTag;
import org.eclipse.packagedrone.utils.rpm.RpmTag;
import org.eclipse.packagedrone.utils.rpm.info.RpmInformation.Dependency;
import org.eclipse.packagedrone.utils.rpm.parse.InputHeader;
import org.eclipse.packagedrone.utils.rpm.parse.RpmInputStream;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.openpgp.PGPException;
public final class RpmInformations
{
@@ -217,6 +222,30 @@ public static String normalize ( final String name )
return name;
}
+ public static String asArmored ( final Object value ) throws PGPException
+ {
+ if ( value == null )
+ {
+ return null;
+ }
+ try
+ {
+ byte[] castVal = ( byte[] ) value;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream ();
+ ArmoredOutputStream aos = new ArmoredOutputStream ( baos );
+ aos.setHeader ( "Version", VersionInformation.VERSIONED_PRODUCT );
+
+ aos.write( castVal );
+ aos.flush ();
+ aos.close ();
+ return new String ( baos.toByteArray () );
+ }
+ catch ( Exception e )
+ {
+ throw new PGPException ( "Error armoring signature data", e );
+ }
+ }
+
public static String asString ( final Object value )
{
if ( value == null )
diff --git a/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/parse/RpmParserStream.java b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/parse/RpmParserStream.java
new file mode 100644
index 00000000..df3108a8
--- /dev/null
+++ b/bundles/org.eclipse.packagedrone.utils.rpm/src/org/eclipse/packagedrone/utils/rpm/parse/RpmParserStream.java
@@ -0,0 +1,349 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Trident Systems, Inc.
+ * This software was developed with U.S government funding in support of the above
+ * contract. Trident grants unlimited rights to modify, distribute and incorporate
+ * our contributions to Eclipse Package Drone bound by the overall restrictions from
+ * the parent Eclipse Public License v1.0 available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Walker Funk - Trident Systems Inc. - initial implementation
+ *******************************************************************************/
+package org.eclipse.packagedrone.utils.rpm.parse;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.eclipse.packagedrone.utils.rpm.RpmBaseTag;
+import org.eclipse.packagedrone.utils.rpm.RpmLead;
+import org.eclipse.packagedrone.utils.rpm.RpmSignatureTag;
+import org.eclipse.packagedrone.utils.rpm.RpmTag;
+import org.eclipse.packagedrone.utils.rpm.Rpms;
+import org.eclipse.packagedrone.utils.rpm.header.*;
+import org.eclipse.packagedrone.utils.rpm.build.PayloadStreamer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.google.common.io.CountingInputStream;
+
+public class RpmParserStream extends InputStream
+{
+ private final static Logger logger = LoggerFactory.getLogger ( RpmParserStream.class );
+
+ private static final byte[] DUMMY = new byte[128];
+
+ private final DataInputStream in;
+
+ private boolean closed;
+
+ private RpmLead lead;
+
+ private Header signatureHeader;
+
+ private Header payloadHeader;
+
+ private PayloadStreamer payloadStreamer;
+
+ private final CountingInputStream count;
+
+ public RpmParserStream ( final InputStream in )
+ {
+ this.count = new CountingInputStream ( in );
+ this.in = new DataInputStream ( this.count );
+ }
+
+ @Override
+ public void close () throws IOException
+ {
+ if ( !this.closed )
+ {
+ this.in.close ();
+ this.closed = true;
+ }
+ }
+
+ protected void ensureInit () throws IOException
+ {
+ if ( this.lead == null )
+ {
+ this.lead = readLead ();
+ }
+
+ if ( this.signatureHeader == null )
+ {
+ this.signatureHeader = readHeader ( true );
+ }
+
+ if ( this.payloadHeader == null )
+ {
+ this.payloadHeader = readHeader ( false );
+ }
+
+ // set up content stream
+ if ( this.payloadStreamer == null )
+ {
+ this.payloadStreamer = new PayloadStreamer( true, this.in );
+ }
+ }
+
+ public PayloadStreamer getPayloadStreamer ()
+ {
+ return this.payloadStreamer;
+ }
+
+ public RpmLead getLead () throws IOException
+ {
+ ensureInit ();
+ return this.lead;
+ }
+
+ public Header getSignatureHeader () throws IOException
+ {
+ ensureInit ();
+ return this.signatureHeader;
+ }
+
+ public Header getPayloadHeader () throws IOException
+ {
+ ensureInit ();
+ return this.payloadHeader;
+ }
+
+ protected RpmLead readLead () throws IOException
+ {
+ final byte[] magic = readComplete ( 4 );
+
+ if ( !Arrays.equals ( magic, Rpms.LEAD_MAGIC ) )
+ {
+ throw new IOException ( String.format ( "File corrupt: Expected magic %s, read: %s", Arrays.toString ( Rpms.LEAD_MAGIC ), Arrays.toString ( magic ) ) );
+ }
+
+ final byte[] version = readComplete ( 2 );
+
+ final short type = this.in.readShort ();
+ final short arch = this.in.readShort ();
+
+ final byte[] nameData = readComplete ( 66 ); // NAME
+
+ final ByteBuffer nameBuffer = ByteBuffer.wrap ( nameData );
+ for ( int i = 0; i < nameData.length; i++ )
+ {
+ if ( nameData[i] == 0 )
+ {
+ nameBuffer.limit ( i );
+ break;
+ }
+ }
+
+ final String name = StandardCharsets.UTF_8.decode ( nameBuffer ).toString ();
+
+ final short os = this.in.readShort ();
+
+ final int sigType = this.in.readUnsignedShort ();
+
+ skipFully ( 16 ); // RESERVED
+
+ return new RpmLead ( version[0], version[1], name, sigType, type, arch, os );
+ }
+
+ protected Header readHeader ( final boolean withPadding ) throws IOException
+ {
+ final long start = this.count.getCount ();
+
+ final byte[] magic = readComplete ( 3 );
+
+ if ( !Arrays.equals ( magic, Rpms.HEADER_MAGIC ) )
+ {
+ throw new IOException ( String.format ( "File corrupt: Expected entry magic %s, read: %s", Arrays.toString ( Rpms.HEADER_MAGIC ), Arrays.toString ( magic ) ) );
+ }
+
+ final byte version = this.in.readByte ();
+ if ( version != 1 )
+ {
+ throw new IOException ( String.format ( "File corrupt: Invalid header entry version: %s (valid: 1)", version ) );
+ }
+
+ skipFully ( 4 ); // RESERVED
+
+ final int indexCount = (this.in.readInt () - 1); // Subtract one to account for immutable
+ final int storeSize = this.in.readInt ();
+
+ skipFully ( 16 ); // IMMUTABLE
+
+ final ByteBuffer tags = ByteBuffer.wrap ( readComplete ( (indexCount * 16) ) );
+ final ByteBuffer store = ByteBuffer.wrap ( readComplete ( storeSize ) );
+
+ final HeaderEntry[] entries = new HeaderEntry[indexCount];
+ for ( int i = 0; i < indexCount; i++ )
+ {
+ entries[i] = readEntry ( tags, store );
+ }
+
+ if ( withPadding )
+ {
+ // pad remaining bytes - to 8
+
+ final int skip = Rpms.padding ( storeSize );
+ if ( skip > 0 )
+ {
+ logger.debug ( "Skipping {} pad bytes", skip );
+ skipFully ( skip );
+ }
+ }
+
+ return new Header<> ( entries );
+ }
+
+ private HeaderEntry readEntry ( ByteBuffer tags, ByteBuffer store ) throws IOException
+ {
+ final int tag = tags.getInt ();
+ final int dataType = tags.getInt ();
+ final int offset = tags.getInt ();
+ final int count = tags.getInt ();
+
+
+ final Type type;
+ byte[] data = null;
+ switch ( dataType )
+ {
+ case 1:
+ type = Type.CHAR;
+ data = new byte[count];
+ store.position ( offset );
+ store.get ( data );
+ break;
+ case 2:
+ type = Type.BYTE;
+ data = new byte[count];
+ store.position ( offset );
+ store.get ( data );
+ break;
+ case 3:
+ type = Type.SHORT;
+ data = new byte[2 * count];
+ store.position ( offset );
+ store.get ( data );
+ break;
+ case 4:
+ type = Type.INT;
+ data = new byte[4 * count];
+ store.position ( offset );
+ store.get ( data );
+ break;
+ case 5:
+ type = Type.LONG;
+ data = new byte[8 * count];
+ store.position ( offset );
+ store.get ( data );
+ break;
+ case 6:
+ type = Type.STRING;
+ data = getBinStringData ( store, offset, 1 );
+ break;
+ case 7:
+ type = Type.BLOB;
+ data = new byte[count];
+ store.position ( offset );
+ store.get ( data );
+ break;
+ case 8:
+ type = Type.STRING_ARRAY;
+ data = getBinStringData ( store, offset, count );
+ break;
+ case 9:
+ type = Type.I18N_STRING;
+ data = getBinStringData ( store, offset, count );
+ break;
+ default:
+ type = Type.NULL; // Null or Unknown (both unsupported)
+ break;
+ }
+ if (data == null)
+ {
+ throw new IOException ( "Null or unknown data type" );
+ }
+ return new HeaderEntry ( type, tag, count, data );
+ }
+
+ private byte[] getBinStringData ( final ByteBuffer store, int offset, final int strings ) throws IOException
+ {
+ int num = 0;
+ store.position ( offset );
+ for ( int i = 0; i < store.remaining (); i++ ) // Find null terminated string or sequence of null terminated strings
+ {
+ if ( store.get ( offset + i ) == 0 )
+ {
+ num++;
+ }
+ if ( num == strings )
+ {
+ byte[] data = new byte[i + 1];
+ store.get ( data );
+ return data;
+ }
+ }
+ throw new IOException ( "Corrupt tag entry" );
+ }
+
+ private byte[] readComplete ( final int size ) throws IOException
+ {
+ final byte[] result = new byte[size];
+ this.in.readFully ( result );
+ return result;
+ }
+
+ private void skipFully ( final int count ) throws IOException
+ {
+ if ( count > 0 )
+ {
+ this.in.readFully ( DUMMY, 0, count );
+ }
+ }
+
+ // forward methods
+
+ @Override
+ public void reset () throws IOException
+ {
+ ensureInit ();
+ this.in.reset ();
+ }
+
+ @Override
+ public int read () throws IOException
+ {
+ ensureInit ();
+ return this.in.read ();
+ }
+
+ @Override
+ public long skip ( final long n ) throws IOException
+ {
+ ensureInit ();
+ return this.in.skip ( n );
+ }
+
+ @Override
+ public int available () throws IOException
+ {
+ ensureInit ();
+ return this.in.available ();
+ }
+
+ @Override
+ public int read ( final byte[] b ) throws IOException
+ {
+ ensureInit ();
+ return this.in.read ( b );
+ }
+
+ @Override
+ public int read ( final byte[] b, final int off, final int len ) throws IOException
+ {
+ return this.in.read ( b, off, len );
+ }
+
+}