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<String, Boolean> FILES_MAP;
private static final String[] FILES_ALL;
static
{
Map<String, Boolean> 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;
}
ProcLogger pl = new ProcLogger();
if ( 0 != Exec.syncAt( 600, pl, "/opt/openslx/ipxe/src", join( "nice", "make", "-j" + cpus,
"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 { // 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 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;
}
class Output
{
protected Map<String, Boolean> 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 );
}
}
}