All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.packager.rpm.build.RpmWriter Maven / Gradle / Ivy

There is a newer version: 0.21.0
Show newest version
/**
 * Copyright (c) 2015, 2019 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.packager.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.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;

import org.eclipse.packager.rpm.RpmLead;
import org.eclipse.packager.rpm.RpmSignatureTag;
import org.eclipse.packager.rpm.RpmTag;
import org.eclipse.packager.rpm.Rpms;
import org.eclipse.packager.rpm.header.Header;
import org.eclipse.packager.rpm.header.Headers;
import org.eclipse.packager.rpm.signature.SignatureProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.ByteStreams;

/**
 * A low level RPM file writer
 * 

* This class handles constructing RPM files. It does not really care about the * contents it writes. Still the content and the format of the content is * important, but this is taken care of by the {@link RpmBuilder}. *

* * @author Jens Reimann */ public class RpmWriter 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 ( RpmWriter.class ); private final FileChannel file; private final RpmLead lead; private final ByteBuffer header; private boolean finished; private PayloadProvider payloadProvider; private final List signatureProcessors = new LinkedList<> (); public RpmWriter ( final Path path, final Supplier leadProvider, final Header header, final Charset headerCharset, final OpenOption... options ) throws IOException { requireNonNull ( path ); requireNonNull ( leadProvider ); requireNonNull ( header ); requireNonNull ( headerCharset ); this.file = FileChannel.open ( path, options != null && options.length > 0 ? options : DEFAULT_OPEN_OPTIONS ); this.lead = leadProvider.get (); this.header = Headers.render ( header.makeEntries (headerCharset), true, Rpms.IMMUTABLE_TAG_HEADER ); } public RpmWriter ( final Path path, final LeadBuilder leadBuilder, final Header header, final OpenOption... options ) throws IOException { this ( path, leadBuilder::build, header, StandardCharsets.UTF_8, options ); } public RpmWriter ( final Path path, final LeadBuilder leadBuilder, final Header header, final Charset headerCharset, final OpenOption... options ) throws IOException { this ( path, leadBuilder::build, header, headerCharset, options ); } public void addSignatureProcessor ( final SignatureProcessor processor ) { this.signatureProcessors.add ( processor ); } public void addAllSignatureProcessors ( final List signatureProcessors ) { this.signatureProcessors.addAll ( signatureProcessors ); } public void setPayload ( final PayloadProvider payloadProvider ) throws IOException { checkNotFinished (); requireNonNull ( payloadProvider ); this.payloadProvider = payloadProvider; } 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 { String name = this.lead.getName (); if ( !Normalizer.isNormalized ( name, Form.NFC ) ) { name = Normalizer.normalize ( name, Form.NFC ); } final byte[] nameEncoded = name.getBytes ( StandardCharsets.UTF_8 ); final byte[] nameData = new byte[66]; System.arraycopy ( nameEncoded, 0, nameData, 0, nameEncoded.length < nameData.length ? nameEncoded.length : nameData.length - 1 ); lead.put ( nameData ); } // 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 (), false, 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 (); } } private void finish () throws IOException { if ( this.finished ) { return; } if ( this.payloadProvider == 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.payloadProvider.getPayloadSize (); debug ( "data - %s - %s", headerSize, payloadSize ); // set signature data final Header signature = new Header<> (); // process signatures processSignatures ( signature ); // write lead writeLead (); // write signature header writeSignatureHeader ( signature ); // 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.payloadProvider.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.packager.rpm.build.RpmWriter.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.payloadProvider.getArchiveSize () ); } // feed the header for ( final SignatureProcessor processor : this.signatureProcessors ) { processor.feedHeader ( this.header.slice () ); } // feed payload data try ( ReadableByteChannel channel = this.payloadProvider.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 ); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy