package org.openslx.taskmanager.tasks; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.ArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.openslx.satserver.util.Archive; import org.openslx.satserver.util.Util; import org.openslx.taskmanager.api.AbstractTask; import com.google.gson.annotations.Expose; public class RecompressArchive extends AbstractTask { @Expose private String[] inputFiles; @Expose private String outputFile; /* * Own vars/constants not being deserialized */ protected static final String[] ALLOWED_DIRS = { "/tmp/", "/opt/openslx/configs/" }; private Output status = new Output(); /* * Code */ @Override protected boolean execute() { String fn = new File( this.outputFile ).getName(); String tmpFile = "/tmp/bwlp-" + System.nanoTime() + "-" + fn; if ( execute2( tmpFile ) ) { try { FileUtils.deleteQuietly( new File( this.outputFile ) ); Files.move( Paths.get( tmpFile ), Paths.get( this.outputFile ) ); return true; } catch ( Exception e ) { status.error = e.toString(); status.errorCode = Output.ErrorCode.WRITE_FAILED; } } FileUtils.deleteQuietly( new File( tmpFile ) ); return false; } private boolean execute2( String archiveFile ) { // Open output file archive TarArchiveOutputStream outArchive = null; try { try { FileUtils.forceMkdir( new File( new File( this.outputFile ).getParent() ) ); outArchive = Archive.createTarArchive( archiveFile ); } catch ( IOException e2 ) { status.error = e2.toString(); status.errorCode = Output.ErrorCode.WRITE_FAILED; return false; } // Open input file archives, one by one for ( final String inputFile : this.inputFiles ) { ArchiveInputStream inArchive = null; try { try { inArchive = Archive.getArchiveInputStream( inputFile ); } catch ( FileNotFoundException e1 ) { status.error = "(" + inputFile + ") " + e1.getMessage(); status.errorCode = Output.ErrorCode.NOT_FOUND; return false; } catch ( IllegalArgumentException e1 ) { status.error = "(" + inputFile + ") " + e1.getMessage(); status.errorCode = Output.ErrorCode.UNKNOWN_ERROR; return false; } catch ( ArchiveException e1 ) { status.error = "(" + inputFile + ") " + e1.getMessage(); status.errorCode = Output.ErrorCode.UNKNOWN_FORMAT; return false; } // It's open ArchiveEntry inEntry; // Remember all files added, so we can issue warnings for duplicate entries Set entries = new HashSet<>(); // Iterate over every entry while ( ( inEntry = inArchive.getNextEntry() ) != null ) { if ( !inArchive.canReadEntryData( inEntry ) ) continue; // Skip unreadable entries // Construct TarArchiveEntry - we want unix stuff like uid/gid, links, file/dir mode, so try to get from source archive TarArchiveEntry outEntry = Archive.createTarArchiveEntry( inEntry, 0, 0, 0755, 0644 ); // Ask lib if the entry can be written if ( !outArchive.canWriteEntryData( outEntry ) ) { status.warnings.add( "Commons-compress says entry '" + outEntry.getName() + "' cannot be written." ); continue; } // Dupcheck if ( entries.contains( outEntry.getName() ) ) { status.warnings.add( "Duplicate entry: " + outEntry.getName() ); } else { entries.add( outEntry.getName() ); } // Actually add entry now try { outArchive.putArchiveEntry( outEntry ); } catch (Exception e) { status.error = "(" + inputFile + ") Could not putArchiveEntry('" + inEntry.getName() + "'): " + e.getMessage(); status.errorCode = Output.ErrorCode.TAR_ERROR; return false; } if ( !Util.streamCopy( inArchive, outArchive, outEntry.getSize() ) ) { status.error = "(" + inputFile + ") Could not copy entry '" + inEntry.getName() + "' to destination archive."; status.errorCode = Output.ErrorCode.WRITE_FAILED; return false; } outArchive.closeArchiveEntry(); } // End entry } catch ( IOException e ) { return false; } finally { Util.multiClose( inArchive ); } } // End of loop over input archives return true; } finally { Util.multiClose( outArchive ); } } @Override protected boolean initTask() { this.setStatusObject( this.status ); // Input files if ( this.inputFiles == null ) { status.error = "Input: No archives given"; status.errorCode = Output.ErrorCode.UNKNOWN_ERROR; return false; } for ( int i = 0; i < this.inputFiles.length; ++i ) { String inputFile = this.inputFiles[i]; if ( inputFile == null || inputFile.length() == 0 || inputFile.charAt( 0 ) != '/' ) { status.error = "Input: Need absolute path (got: " + inputFile + ")"; status.errorCode = Output.ErrorCode.INVALID_DIRECTORY; return false; } inputFile = FilenameUtils.normalize( inputFile ); if ( inputFile == null || !Util.startsWith( inputFile, ALLOWED_DIRS ) ) { status.error = "Input: File not in allowed directory"; status.errorCode = Output.ErrorCode.INVALID_DIRECTORY; return false; } this.inputFiles[i] = inputFile; } // Output file if ( this.outputFile == null || this.outputFile.length() == 0 || this.outputFile.charAt( 0 ) != '/' ) { status.error = "Output: Need absolute path."; status.errorCode = Output.ErrorCode.INVALID_DIRECTORY; return false; } this.outputFile = FilenameUtils.normalize( this.outputFile ); if ( !Util.startsWith( this.outputFile, ALLOWED_DIRS ) ) { status.error = "Output: File not in allowed directory"; status.errorCode = Output.ErrorCode.INVALID_DIRECTORY; return false; } return true; } @SuppressWarnings( "unused" ) private static class Output { protected String error = null; protected enum ErrorCode { NOT_FOUND, UNKNOWN_FORMAT, UNKNOWN_ERROR, INVALID_DIRECTORY, WRITE_FAILED, TAR_ERROR }; protected ErrorCode errorCode = null; protected List warnings = new CopyOnWriteArrayList<>(); public static class Entry { protected String name = null; protected boolean isdir = false; protected long size = -1; } } }