package org.openslx.taskmanager.tasks; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.openslx.satserver.util.Exec; import org.openslx.satserver.util.Exec.ExecCallback; import org.openslx.taskmanager.api.AbstractTask; import org.openslx.taskmanager.api.BoundedLog; import com.google.gson.annotations.Expose; public class CompileIPxeNew extends AbstractTask { @Expose private String ipaddress = null; private Output status = new Output(); private static AtomicBoolean isRunning = new AtomicBoolean(); /** * Files which should be copied to the TFTP dir so they're available for netboot */ private static final String[] FILES_NET = { "bin-i386-pcbios/ipxe.pxe", "bin-i386-pcbios/undionly.kpxe", "bin-i386-pcbios/undionly.kkpxe", "bin-i386-pcbios/undionly.kkkpxe", "bin-x86_64-efi/ipxe.efi", "bin-x86_64-efi/snponly.efi" }; /** * Files which should be moved to the websrv so they're available for download */ private static final String[] FILES_DL = { "bin-i386-pcbios/ipxe.usb", "bin-i386-pcbios/ipxe.hd", "bin-i386-pcbios/ipxe.lkrn", "bin-x86_64-efi/ipxe.usb", "bin-x86_64-efi/ipxe.efi", "bin-x86_64-efi/snp.usb", "bin-x86_64-efi/snp.efi" }; /** * Source directory for our iPXE config */ private static final String CONFIG_SRC_DIR = "/opt/openslx/ipxe-bwlp-config"; /** * Destination directory for iPXE config */ private static final String CONFIG_DST_DIR = "/opt/openslx/ipxe/src/config/local/bwlp"; /** * Combination of the two, mapping each file to false. * Will be used for status object. */ private static final Map FILES_MAP; private static final String[] FILES_ALL; static { Map map = new HashMap<>(); for ( String s : FILES_NET ) { map.put( s, false ); } for ( String s : FILES_DL ) { map.put( s, false ); } FILES_MAP = Collections.unmodifiableMap( map ); FILES_ALL = map.keySet().toArray( new String[ map.size() ] ); } @Override protected boolean initTask() { this.setStatusObject( this.status ); if ( this.ipaddress == null || this.ipaddress.isEmpty() ) { status.addError( "No IP address given!" ); return false; } return true; } @Override protected boolean execute() { if ( !isRunning.compareAndSet( false, true ) ) { status.addError( "Another operation is already in progress." ); return false; } try { boolean ret = true; if ( !updateIpxe() ) ret = false; return ret; } finally { isRunning.set( false ); } } private boolean updateIpxe() { // Prepare menu String template; try { template = FileUtils.readFileToString( new File( "./data/ipxe-embed.template" ), StandardCharsets.UTF_8 ); } catch ( IOException e ) { status.addError( e.toString() ); return false; } // Substitution template = template.replace( "%ipaddress%", this.ipaddress ); String hybridEmbed = template.replace( "%mode%", "PXE" ); String usbEmbed = template.replace( "%mode%", "USB" ); // Write out try { FileUtils.writeStringToFile( new File( "/opt/openslx/ipxe/ipxelinux.ipxe" ), hybridEmbed, StandardCharsets.UTF_8 ); FileUtils.writeStringToFile( new File( "/opt/openslx/ipxe/usb.ipxe" ), usbEmbed, StandardCharsets.UTF_8 ); } catch ( IOException e ) { status.addError( e.toString() ); return false; } // Compile int cpus = Runtime.getRuntime().availableProcessors(); if (cpus < 1) { cpus = 1; } else if (cpus > 256) { // Sanity check in case it (apparently) reports nonsense cpus = 4; } // Copy over config again if ( !copyBwlpConfig() ) { status.addError( "Aborting as config could not be copied to " + CONFIG_DST_DIR ); return false; } // Use NO_WERROR so older commits compile on newer gcc versions if ( 0 != Exec.syncAt( 600, new ProcLogger(), "/opt/openslx/ipxe/src", join( "nice", "make", "-j" + cpus, "NO_WERROR=1", "CONFIG=bwlp", "EMBED=../ipxelinux.ipxe", FILES_ALL ) ) ) { status.addError( "Compiling ipxe targets failed" ); return false; } Exec.syncAt( 1, new ProcVersion(), "/opt/openslx/ipxe", "git", "rev-parse", "HEAD" ); // NETBOOT for ( String f : FILES_NET ) { String destName = new File( f ).getName(); if ( f.contains( "-pcbios" ) ) { // Append .0 to filename, otherwise older pxelinux won't be able to chain to us destName += ".0"; } try { FileUtils.copyFile( new File( "/opt/openslx/ipxe/src", f ), new File( "/srv/openslx/tftp", destName ) ); } catch ( Exception e ) { status.addError( "Cannot copy " + f + " to TFTP dir: " + e.toString() ); } } // Change ipxelinux.0 so old DHCP entries keep working Path link = Paths.get( "/srv/openslx/tftp/ipxelinux.0" ); FileUtils.deleteQuietly( link.toFile() ); try { // Use kkkpxe since it inits faster and didn't cause any trouble (yet) Files.createSymbolicLink( link, Paths.get( "undionly.kkkpxe.0" ) ); } catch ( Exception e1 ) { status.addError( "Could not create ipxelinux.0 symlink" ); } // DOWNLOAD for ( String f : FILES_DL ) { try { FileUtils.copyFile( new File( "/opt/openslx/ipxe/src/", f ), new File( "/srv/openslx/www/boot/download", f.replace( '/', '-' ) ) ); } catch ( Exception e ) { status.addError( "Cannot copy " + f + " to www-download dir: " + e.toString() ); } } return true; } private boolean copyBwlpConfig() { File srcDir = new File( CONFIG_SRC_DIR ); File[] files = srcDir.listFiles(); if ( files == null ) return false; File dstDir = new File( CONFIG_DST_DIR ); try { Files.createDirectories( dstDir.toPath() ); } catch ( Exception e ) { status.addError( e.getMessage() ); return false; } for ( File file : files ) { if ( !file.isFile() || !file.getName().endsWith( ".h" ) ) continue; try { Files.copy( file.toPath(), new File( dstDir, file.getName() ).toPath(), StandardCopyOption.REPLACE_EXISTING ); } catch ( Exception e ) { status.addError( "Cannot copy: " + e.getMessage() ); } } return true; } private String[] join(Object... stuff) { int num = 0; for ( Object o : stuff ) { if ( o instanceof String[] ) { num += ( (String[])o ).length; } else if ( o instanceof String ) { num++; } else { try { status.addLog( "Ignoring non-String join argument '" + o + "'" ); } catch ( Exception e ) {} // o.toString() failed... screw it... } } String[] r = new String[ num ]; num = 0; for ( Object o : stuff ) { if ( o instanceof String[] ) { System.arraycopy( (String[])o, 0, r, num, ( (String[])o ).length ); num += ( (String[])o ).length; } else if ( o instanceof String ) { r[num++] = (String)o; } } return r; } static class Output { protected Map files = new ConcurrentHashMap<>( FILES_MAP ); protected final BoundedLog log = new BoundedLog( 20, true ); protected String errors = ""; protected String hash; protected void addLog( String data ) { log.addLog( data ); } protected synchronized void addError( String err ) { errors = errors + err + "\n"; } } private static final Pattern RE_FINISH = Pattern.compile( "^\\s*\\[(?:FINISH|GENEFIDSK)\\]\\s*(.*?)\\s*$" ); private class ProcLogger implements ExecCallback { @Override public void processStdOut( String line ) { status.addLog( line ); Matcher m = RE_FINISH.matcher( line ); if ( m.matches() ) { status.files.put( m.group( 1 ), true ); } } @Override public void processStdErr( String line ) { status.addLog( line ); } } class ProcVersion implements ExecCallback { @Override public void processStdOut( String line ) { if ( line.length() >= 40 && !line.contains( " " ) ) { status.hash = line; } } @Override public void processStdErr( String line ) { } } }