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.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.apache.log4j.Logger; 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 { private static final Logger LOG = Logger.getLogger( CompileIPxeNew.class ); @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" }; /** * 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 ProcLogger pl = new ProcLogger(); if ( 0 != Exec.syncAt( 600, pl, "/opt/openslx/ipxe/src", join( "make", "EMBED=../ipxelinux.ipxe", FILES_ALL ) ) ) { status.addError( "Compiling ipxe targets failed" ); return false; } // 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 { Files.createSymbolicLink( link, Paths.get( "undionly.kpxe.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 String[] join(String a, String b, String... rest) { String[] r = new String[rest.length + 2]; r[0] = a; r[1] = b; System.arraycopy( rest, 0, r, 2, rest.length ); return r; } class Output { protected Map files = new ConcurrentHashMap<>( FILES_MAP ); protected final BoundedLog log = new BoundedLog( 20, true ); protected String errors = ""; 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 ); } } }