From 15127b0fd463a51897aa1c760d4fc54ee1315c65 Mon Sep 17 00:00:00 2001 From: peeqle Date: Mon, 16 Jun 2025 11:56:00 +0300 Subject: [PATCH 1/5] feat: Replaced fetching configuration strategy with strictly defined path of resource configuration Added file configuration parameter to the MachineConfiguration static context load for configuration eliminated ObjectArchiveCalls --- .../common/EmulationEnvironmentHelper.java | 37 +- .../bwfla/emucomp/common/FileCollection.java | 169 ++--- .../emucomp/common/MachineConfiguration.java | 92 +-- .../de/bwl/bwfla/emucomp/NodeManager.java | 3 + .../emucomp/components/BindingsManager.java | 685 +++++++++--------- .../emucomp/components/BindingsResolver.java | 96 +++ .../main/resources/config/test-machine.json | 210 +++--- 7 files changed, 644 insertions(+), 648 deletions(-) create mode 100644 emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java diff --git a/commons/src/main/java/de/bwl/bwfla/emucomp/common/EmulationEnvironmentHelper.java b/commons/src/main/java/de/bwl/bwfla/emucomp/common/EmulationEnvironmentHelper.java index c5870e1..8eee3dd 100755 --- a/commons/src/main/java/de/bwl/bwfla/emucomp/common/EmulationEnvironmentHelper.java +++ b/commons/src/main/java/de/bwl/bwfla/emucomp/common/EmulationEnvironmentHelper.java @@ -135,15 +135,14 @@ public static MachineConfiguration clean(final MachineConfiguration original, bo continue; } -// if (r instanceof ImageArchiveBinding) -// { -// -// if(env.checkpointBindingId == null || env.checkpointBindingId.isEmpty()) { -// ImageArchiveBinding iab = (ImageArchiveBinding) r; -// if (iab.getId().equals("emucon-rootfs")) -// it.remove(); -// } -// } + if (r instanceof ImageArchiveBinding) + { + if(env.checkpointBindingId == null || env.checkpointBindingId.isEmpty()) { + ImageArchiveBinding iab = (ImageArchiveBinding) r; + if (iab.getId().equals("emucon-rootfs")) + it.remove(); + } + } // resource was volatile (but not in use by a drive), remove it, too for (AbstractDataResource origRes : original.getAbstractDataResource()) { @@ -181,26 +180,6 @@ private static String _registerDataSource(String conf, String ref, String type) } } -// /** -// * Drives capable to accept ready made images. -// * -// * @return -// */ -// public static List getImageDrives(MachineConfiguration env) { -// List emptyDrives = new ArrayList<>(); -// Iterator iterator = env.getDrive().iterator(); -// -// while (iterator.hasNext()) { -// Drive d = iterator.next(); -// if (d.getData() == null || d.getData().isEmpty()) { -// Drive.DriveType type = d.getType(); -// emptyDrives.add(type.name()); -// } -// } -// -// return emptyDrives; -// } - /** * Drives require a file system helper (FS annotation is required) * diff --git a/commons/src/main/java/de/bwl/bwfla/emucomp/common/FileCollection.java b/commons/src/main/java/de/bwl/bwfla/emucomp/common/FileCollection.java index b694266..27c460c 100755 --- a/commons/src/main/java/de/bwl/bwfla/emucomp/common/FileCollection.java +++ b/commons/src/main/java/de/bwl/bwfla/emucomp/common/FileCollection.java @@ -1,113 +1,94 @@ package de.bwl.bwfla.emucomp.common; import de.bwl.bwfla.emucomp.common.utils.jaxb.JaxbType; +import lombok.Getter; import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.*; import java.util.ArrayList; import java.util.List; +@Getter @XmlAccessorType(XmlAccessType.FIELD) -@XmlType(name = "fileCollection", namespace="http://bwfla.bwl.de/common/datatypes") +@XmlType(name = "fileCollection", namespace = "http://bwfla.bwl.de/common/datatypes") @XmlRootElement(namespace = "http://bwfla.bwl.de/common/datatypes") public class FileCollection extends JaxbType { - @XmlElement(name="file", namespace="http://bwfla.bwl.de/common/datatypes") - public List files = new ArrayList(); - - @XmlElement(namespace="http://bwfla.bwl.de/common/datatypes") - public String id; - - @XmlElement - private String archive; - - @XmlElement - private String label; - - public FileCollection() - { - id = null; - } - - public FileCollection(String id) - { - this.id = id; - } - + @XmlElement(name = "file", namespace = "http://bwfla.bwl.de/common/datatypes") + public List files = new ArrayList(); + + @XmlElement(namespace = "http://bwfla.bwl.de/common/datatypes") + public String id; + + @XmlElement + private String archive; + + @XmlElement + private String label; + + public FileCollection() { + id = null; + } + + public FileCollection(String id) { + this.id = id; + } + + public FileCollection(String id, String archive, String label) { + this.id = id; + this.archive = archive; + this.label = label; + } + public static FileCollection fromValue(String data) throws JAXBException { return JaxbType.fromValue(data, FileCollection.class); } - public FileCollectionEntry getDefaultEntry() - { - for(FileCollectionEntry fc : files) - if(fc.isDefault()) - return fc; - - return files.get(0); - } - - public FileCollection copy() - { - try { - return fromValue(this.value()); - } - catch(JAXBException e) { - // impossible - return null; - } + public FileCollectionEntry getDefaultEntry() { + for (FileCollectionEntry fc : files) + if (fc.isDefault()) + return fc; + + return files.get(0); } - public void update() - { - for (final var file : files) { - file.setArchive(this.archive); - file.setObjectId(this.id); - } - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public String getArchive() - { - return archive; - } - - public void setArchive(String archive) - { - this.archive = archive; - } - - public FileCollectionEntry find(String resourceId) - { - for (final var resource : files) { - if (!resourceId.equalsIgnoreCase(resource.getId())) - continue; - - return resource; - } - - return null; - } - - public String resolve(String exportUrlPrefix, String resourceId) - { - final var resource = this.find(resourceId); - return (resource != null) ? resource.resolve(exportUrlPrefix) : null; - } - - public boolean contains(Binding.ResourceType rt) - { - for (final var resource : files) { - if (resource.getResourceType() == rt) - return true; - } - - return false; - } + public FileCollection copy() { + try { + return fromValue(this.value()); + } catch (JAXBException e) { + // impossible + return null; + } + } + + public void update() { + for (final var file : files) { + file.setArchive(this.archive); + file.setObjectId(this.id); + } + } + + public FileCollectionEntry find(String resourceId) { + for (final var resource : files) { + if (!resourceId.equalsIgnoreCase(resource.getId())) + continue; + + return resource; + } + + return null; + } + + public String resolve(String exportUrlPrefix, String resourceId) { + final var resource = this.find(resourceId); + return (resource != null) ? resource.resolve(exportUrlPrefix) : null; + } + + public boolean contains(Binding.ResourceType rt) { + for (final var resource : files) { + if (resource.getResourceType() == rt) + return true; + } + + return false; + } } diff --git a/commons/src/main/java/de/bwl/bwfla/emucomp/common/MachineConfiguration.java b/commons/src/main/java/de/bwl/bwfla/emucomp/common/MachineConfiguration.java index bdb319c..80e6cdf 100755 --- a/commons/src/main/java/de/bwl/bwfla/emucomp/common/MachineConfiguration.java +++ b/commons/src/main/java/de/bwl/bwfla/emucomp/common/MachineConfiguration.java @@ -21,6 +21,9 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import de.bwl.bwfla.emucomp.common.utils.jaxb.JaxbType; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.*; @@ -30,6 +33,8 @@ import java.util.logging.Logger; +@Getter +@Setter @JsonTypeName("emulationEnvironment") @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "emulationEnvironment", namespace = "http://bwfla.bwl.de/common/datatypes", propOrder = { @@ -57,6 +62,7 @@ public class MachineConfiguration protected EmulatorSpec emulator; @XmlElement(namespace = "http://bwfla.bwl.de/common/datatypes", required = false) protected String model; + @lombok.Setter @XmlElement(name = "ui_options", namespace = "http://bwfla.bwl.de/common/datatypes") protected UiOptions uiOptions; @XmlElement(namespace = "http://bwfla.bwl.de/common/datatypes") @@ -73,6 +79,8 @@ public class MachineConfiguration @XmlElementRef(name = "objectArchiveBinding", type = ObjectArchiveBinding.class, namespace = "http://bwfla.bwl.de/common/datatypes")}) protected List abstractDataResource; + protected List attachedFiles; + @XmlElement(namespace = "http://bwfla.bwl.de/common/datatypes") protected NativeConfig nativeConfig; @@ -108,48 +116,9 @@ protected MachineConfiguration(MachineConfigurationTemplate template) { public MachineConfiguration() { } - public String getOperatingSystemId() { - return operatingSystemId; - } - - public void setOperatingSystemId(String operatingSystemId) { - this.operatingSystemId = operatingSystemId; - } - @XmlElement(namespace = "http://bwfla.bwl.de/common/datatypes", name = "installedSoftwareId") protected List installedSoftwareIds = new ArrayList(); - public String getArch() { - return arch; - } - - public void setArch(String value) { - this.arch = value; - } - - public String getModel() { - return model; - } - - public void setModel(String value) { - this.model = value; - } - - public EmulatorSpec getEmulator() { - return emulator; - } - - public void setEmulator(EmulatorSpec value) { - this.emulator = value; - } - - public UiOptions getUiOptions() { - return uiOptions; - } - - public void setUiOptions(UiOptions value) { - this.uiOptions = value; - } public List getDrive() { if (drive == null) { @@ -158,10 +127,6 @@ public List getDrive() { return this.drive; } - public void setDrive(List drives) { - this.drive = drives; - } - public List getNic() { if (nic == null) { nic = new ArrayList(); @@ -169,10 +134,6 @@ public List getNic() { return this.nic; } - public void setAbstractDataResource(List abstractDataResource) { - this.abstractDataResource = abstractDataResource; - } - public List getAbstractDataResource() { if (abstractDataResource == null) { abstractDataResource = new ArrayList(); @@ -180,23 +141,6 @@ public List getAbstractDataResource() { return this.abstractDataResource; } - - public NativeConfig getNativeConfig() { - return nativeConfig; - } - - public void setNativeConfig(NativeConfig value) { - this.nativeConfig = value; - } - - public List getInstalledSoftwareIds() { - return installedSoftwareIds; - } - - public void setInstalledSoftwareIds(List ids) { - this.installedSoftwareIds = ids; - } - public boolean hasCheckpointBindingId() { return (checkpointBindingId != null && !checkpointBindingId.isEmpty()); } @@ -213,26 +157,6 @@ public String getCheckpointBindingId(boolean stripped) { return checkpointBindingId; } - public void setCheckpointBindingId(String checkpointId) { - this.checkpointBindingId = checkpointId; - } - - public String getOutputBindingId() { - return outputBindingId; - } - - public boolean isLinuxRuntime() { - return isLinuxRuntime; - } - - public void setLinuxRuntime(boolean linuxRuntime) { - isLinuxRuntime = linuxRuntime; - } - - public void setOutputBindingId(String bindingId) { - this.outputBindingId = bindingId; - } - @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "value" diff --git a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/NodeManager.java b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/NodeManager.java index f4f0674..d18772c 100755 --- a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/NodeManager.java +++ b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/NodeManager.java @@ -27,6 +27,7 @@ import de.bwl.bwfla.emucomp.common.logging.PrefixLogger; import de.bwl.bwfla.emucomp.common.utils.ConfigHelpers; import de.bwl.bwfla.emucomp.components.AbstractEaasComponent; +import de.bwl.bwfla.emucomp.components.BindingsResolver; import de.bwl.bwfla.emucomp.components.emulators.EmulatorBean; import de.bwl.bwfla.emucomp.components.network.NetworkSwitchBean; import de.bwl.bwfla.emucomp.components.network.NodeTcpBean; @@ -102,6 +103,8 @@ public void init() throws BWFLAException { if (is == null) { throw new FileNotFoundException("Resource not found: " + componentDefaultConfigInitUri); } + BindingsResolver.providedConfigurationPath = componentDefaultConfigInitUri; + String data = new String(is.readAllBytes(), StandardCharsets.UTF_8); loadedComponentConfiguration.set(objectMapper.readValue(data, ComponentConfiguration.class)); diff --git a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsManager.java b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsManager.java index 51bf374..97c85b2 100755 --- a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsManager.java +++ b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsManager.java @@ -33,359 +33,344 @@ import java.util.logging.Logger; import java.util.stream.Stream; - -public class BindingsManager -{ - private final Logger log; - - private final Map bindings; - private final Map paths; - private final ImageMounter imageMounter; - - //TODO REMOVE MOCKED - private static final Object objectArchiveHelper = null; -// static { -// final var objectArchiveAddress = ConfigurationProvider.getConfiguration() -// .get("ws.objectarchive"); -// -// objectArchiveHelper = new ObjectArchiveHelper(objectArchiveAddress); -// } - - public enum EntryType - { - IMAGE("image"), - ALIAS("alias"), - FS_MOUNT("fsmnt"), - RAW_MOUNT("rawmnt"); - - private final String value; - - EntryType(String value) - { - this.value = value; - } - } - - private static final String ID_SEPARATOR = "/_____"; - - public BindingsManager() - { - this(Logger.getLogger(BindingsManager.class.getName())); - } - - public BindingsManager(Logger log) - { - this.bindings = new HashMap(); - this.paths = new LinkedHashMap(); - this.log = log; - this.imageMounter = new ImageMounter(log); - } - - /** Returns all registered bindings: binding's ID -> binding object */ - public Map entries() - { - return Collections.unmodifiableMap(bindings); - } - - /** Returns all mountpoints: binding's ID -> mountpoint */ - public Map mountpoints() - { - return Collections.unmodifiableMap(paths); - } - - /** Returns all mountpoints: binding's ID -> binding's access path */ - public Stream> paths() - { - return paths.entrySet().stream() - .filter((entry) -> !entry.getKey().contains(ID_SEPARATOR)); - } - - /** Returns a binding by its ID. */ - public Binding get(String id) - { - return bindings.get(id); - } - - /** Registers a new binding. */ - public void register(AbstractDataResource resource) throws BWFLAException - { - final String id = resource.getId(); - if (id == null || id.isEmpty()) - throw new IllegalArgumentException("Invalid resource's ID!"); - - if (resource instanceof Binding) { - this.put(id, (Binding) resource); - } - else if (resource instanceof ObjectArchiveBinding) { - // If the resource is an ArchiveBinding, query the archive - // and add all entries from the file collection - final ObjectArchiveBinding object = (ObjectArchiveBinding) resource; - final FileCollection fc = null; -// objectArchiveHelper.getObjectReference(object.getArchive(), object.getId()); - if (fc == null || fc.id == null || fc.id.isEmpty()) - throw new BWFLAException("Retrieving object meta data failed!"); - - for (FileCollectionEntry link : fc.files) { - if (link.getId() == null || link.getUrl() == null) - continue; - - this.put(id + "/" + link.getId(), link); - } - } - else { - final String clazz = resource.getClass().getName(); - throw new IllegalArgumentException("Unsupported resource type: " + clazz); - } - } - - /** Returns path to the binding's image, or null if not mounted or avalilable. */ - public String lookup(String binding) - { - final String prefix = "binding://"; - if (binding.startsWith(prefix)) - binding = binding.substring(prefix.length()); - - return paths.get(binding); - } - - /** Returns a collection of all binding-ids starting with prefix. */ - public Stream find(String prefix) - { - return bindings.keySet().stream() - .filter((id) -> id.startsWith(prefix)); - } - - private static void prepareResourceBinding(String componentId, Binding resource) throws IllegalArgumentException - { +import static de.bwl.bwfla.emucomp.components.BindingsResolver.findFileCollectionDeclaration; + + +public class BindingsManager { + private final Logger log; + + private final Map bindings; + private final Map paths; + private final ImageMounter imageMounter; + + public enum EntryType { + IMAGE("image"), + ALIAS("alias"), + FS_MOUNT("fsmnt"), + RAW_MOUNT("rawmnt"); + + private final String value; + + EntryType(String value) { + this.value = value; + } + } + + private static final String ID_SEPARATOR = "/_____"; + + public BindingsManager() { + this(Logger.getLogger(BindingsManager.class.getName())); + } + + public BindingsManager(Logger log) { + this.bindings = new HashMap(); + this.paths = new LinkedHashMap(); + this.log = log; + this.imageMounter = new ImageMounter(log); + } + + /** + * Returns all registered bindings: binding's ID -> binding object + */ + public Map entries() { + return Collections.unmodifiableMap(bindings); + } + + /** + * Returns all mountpoints: binding's ID -> mountpoint + */ + public Map mountpoints() { + return Collections.unmodifiableMap(paths); + } + + /** + * Returns all mountpoints: binding's ID -> binding's access path + */ + public Stream> paths() { + return paths.entrySet().stream() + .filter((entry) -> !entry.getKey().contains(ID_SEPARATOR)); + } + + /** + * Returns a binding by its ID. + */ + public Binding get(String id) { + return bindings.get(id); + } + + /** + * Registers a new binding. + */ + public void register(AbstractDataResource resource) throws BWFLAException { + final String id = resource.getId(); + if (id == null || id.isEmpty()) + throw new IllegalArgumentException("Invalid resource's ID!"); + + if (resource instanceof Binding) { + this.put(id, (Binding) resource); + } else if (resource instanceof ObjectArchiveBinding) { + // If the resource is an ArchiveBinding, query the archive + // and add all entries from the file collection + final ObjectArchiveBinding object = (ObjectArchiveBinding) resource; + final Optional fc = findFileCollectionDeclaration(object.getArchive(), object.getId()); + if (fc.isEmpty()) + throw new BWFLAException("Retrieving object meta data failed!"); + FileCollection collection = fc.get(); + if (collection.getId() == null || collection.getId().isEmpty()) { + throw new BWFLAException("Retrieving object meta data failed! - Object is not defined properly!\n" + collection); + } + for (FileCollectionEntry link : collection.files) { + if (link.getId() == null || link.getUrl() == null) + continue; + + this.put(id + "/" + link.getId(), link); + } + } else { + final String clazz = resource.getClass().getName(); + throw new IllegalArgumentException("Unsupported resource type: " + clazz); + } + } + + /** + * Returns path to the binding's image, or null if not mounted or avalilable. + */ + public String lookup(String binding) { + final String prefix = "binding://"; + if (binding.startsWith(prefix)) + binding = binding.substring(prefix.length()); + + return paths.get(binding); + } + + /** + * Returns a collection of all binding-ids starting with prefix. + */ + public Stream find(String prefix) { + return bindings.keySet().stream() + .filter((id) -> id.startsWith(prefix)); + } + + private static void prepareResourceBinding(String componentId, Binding resource) throws IllegalArgumentException { /* ImageArchive Bindings contain no valid URLs, just image IDs We delegate the resolving image IDs to the proxy. Other resources may contain valid URLs. */ - if (resource instanceof ImageArchiveBinding) - { - final var binding = (ImageArchiveBinding) resource; - final var location = DataResolvers.resolve(componentId, binding); - resource.setUrl(location); - } - - // Resolve object-archive's binding URLs! - if (resource instanceof FileCollectionEntry) - { - final var location = DataResolvers.objects() - .resolve(componentId, (FileCollectionEntry) resource); - - resource.setUrl(location); - } - - if (resource.getId() == null - || resource.getId().isEmpty() - || resource.getUrl() == null - || resource.getUrl().isEmpty()) - throw new IllegalArgumentException( - "Given resource is null, has invalid id or empty url."); - - if (resource.getAccess() == null) - resource.setAccess(Binding.AccessType.COW); - } - - /** - * Resolves and mounts a binding location of either the form - * binding://> or . - * - * The is replaced with the actual filesystem location of the binding's mountpoint. - * - * @param binding A binding location - * @return The resolved path or null, if the binding cannot be found - */ - public String mount(String componentId, String binding, Path outdir) - throws BWFLAException, IOException, IllegalArgumentException - { - if (binding == null || binding.isEmpty()) - throw new IllegalArgumentException("Binding is null or empty!"); - - if (binding.startsWith("rom://")) - binding = "rom-" + binding.substring("rom://".length()); - - if (binding.startsWith("binding://")) - binding = binding.substring("binding://".length()); - - // if (binding.contains("/")) - // throw new BWFLAException("Subresource bindings are currently not supported!"); - - // Let's see if we already have the resource mounted - String resourcePath = this.lookup(binding); - if (resourcePath != null) - return resourcePath; - - log.info("Resolving resource '" + binding + "'..."); - - final String realBindingId = binding; - final Binding resource = bindings.get(realBindingId); - if (resource == null) - throw new BWFLAException("Could not find binding for resource " + binding); - - log.info("Mounting binding '" + binding + "'..."); - - prepareResourceBinding(componentId, resource); - - // TODO: we need to resolve the full path here or earlier to - // ensure that all access options use the same path: - if(binding.startsWith("rom-")) // old rom bindings do not have COPY access by default - { - log.info("guessing resource is a ROM. force COPY access if not QCOW"); - - // check the file type first. - ImageInformation inf = new ImageInformation(resource.getUrl(), log); - ImageInformation.QemuImageFormat fmt = inf.getFileFormat(); - if(fmt != ImageInformation.QemuImageFormat.QCOW2) - resource.setAccess(Binding.AccessType.COPY); - } - - final MountOptions mountOpts = new MountOptions(); - if (resource.getFileSize() > 0) - mountOpts.setSize(resource.getFileSize()); - - Path imgPath = null; - ImageMounter.Mount mount = null; - switch (resource.getAccess()) { - case COW: - imgPath = outdir.resolve(realBindingId + ".cow"); - - QcowOptions qcowOptions = new QcowOptions(); - qcowOptions.setBackingFile(resource.getUrl()); - - EmulatorUtils.createCowFile(imgPath, qcowOptions); - - Path rawImagePath = outdir.resolve(realBindingId + ".dd"); - mount = imageMounter.mount(imgPath, rawImagePath, mountOpts); - - this.put(realBindingId, EntryType.RAW_MOUNT, resourcePath); - resourcePath = mount.getMountPoint().toAbsolutePath().toString(); - break; - case COPY: - imgPath = outdir.resolve(realBindingId + ".copy"); - EmulatorUtils.copyRemoteUrl(resource, imgPath, log); - resourcePath =imgPath.toString(); - break; - } - this.put(realBindingId, EntryType.IMAGE, imgPath.toAbsolutePath().toString()); - - // resourcePath is now the base path for the binding we want to find - if (resourcePath == null || !(new File(resourcePath).canRead())) { - final String message = "Binding target at location " - + resource.getUrl() + " cannot be accessed!"; - - throw new BWFLAException(message); - } - - // Is the raw file a block device with a filesystem? - final String fsType = this.getImageFileSystem(resource); - if (mount != null && fsType != null && !fsType.isEmpty()) { - final Path mountpoint = outdir.resolve(realBindingId + "." + fsType.replace(',', '.') + ".fuse"); - imageMounter.mount(mount, mountpoint, FileSystemType.fromString(fsType)); - resourcePath = mountpoint.toString(); - this.put(realBindingId, EntryType.FS_MOUNT, resourcePath); - } - - // Is local alias specified? - final String alias = resource.getLocalAlias(); - if (alias != null) { - final Path link = outdir.resolve(alias); - Files.deleteIfExists(link); - Files.createSymbolicLink(link, Paths.get(resourcePath)); - resourcePath = link.toString(); - - this.put(realBindingId, EntryType.ALIAS, resourcePath); - } - - this.add(realBindingId, resourcePath); - return resourcePath; - } - - /** Unmounts all registered bindings */ - public void cleanup() - { - log.info("Unmounting bindings..."); - { - final Set idsToRemove = new HashSet(); - - final String[] fuseIdSuffixes = new String[] { - ID_SEPARATOR + EntryType.RAW_MOUNT.value, - ID_SEPARATOR + EntryType.FS_MOUNT.value, - }; - - paths.forEach((id, path) -> { - for (String suffix : fuseIdSuffixes) { - if (!id.endsWith(suffix)) - continue; // Not a FUSE-mount! - - final int end = id.length() - suffix.length(); - idsToRemove.add(id.substring(0, end)); - } - }); - - imageMounter.unmount(); - idsToRemove.forEach((id) -> this.remove(id)); - idsToRemove.clear(); - } - } - - public static String toBindingId(String base, EntryType type) - { - return (base + ID_SEPARATOR + type.value); - } - - - /* =============== Internal Helpers =============== */ - - private void put(String id, Binding binding) - { - bindings.put(id, binding); - - log.info("Added binding: " + id); - } - - private void put(String base, EntryType type, String path) - { - final String id = BindingsManager.toBindingId(base, type); - paths.put(id, path); - - log.info("Added binding's path entry: " + id + " -> " + path); - } - - private void add(String id, String path) - { - paths.put(id, path); - - log.info("Added binding's access path: " + id + " -> " + path); - } - - private void remove(String id) - { - bindings.remove(id); - paths.remove(id); - for (EntryType type : EntryType.values()) - paths.remove(BindingsManager.toBindingId(id, type)); - } - - private String getImageFileSystem(Binding resource) - { - String fstype = null; - - if (resource instanceof ImageArchiveBinding) { - final ImageArchiveBinding image = (ImageArchiveBinding) resource; - fstype = image.getFileSystemType(); - } - else if (resource instanceof BlobStoreBinding) { - final BlobStoreBinding image = (BlobStoreBinding) resource; - if (image.getFileSystemType() != null && image.getMountFS()) - fstype = image.getFileSystemType().toString(); - } - - if (fstype != null) - fstype = fstype.toLowerCase(); - - return fstype; - } + if (resource instanceof ImageArchiveBinding) { + final var binding = (ImageArchiveBinding) resource; + final var location = DataResolvers.resolve(componentId, binding); + resource.setUrl(location); + } + + // Resolve object-archive's binding URLs! + if (resource instanceof FileCollectionEntry) { + final var location = DataResolvers.objects() + .resolve(componentId, (FileCollectionEntry) resource); + + resource.setUrl(location); + } + + if (resource.getId() == null + || resource.getId().isEmpty() + || resource.getUrl() == null + || resource.getUrl().isEmpty()) + throw new IllegalArgumentException( + "Given resource is null, has invalid id or empty url."); + + if (resource.getAccess() == null) + resource.setAccess(Binding.AccessType.COW); + } + + /** + * Resolves and mounts a binding location of either the form + * binding://> or . + *

+ * The is replaced with the actual filesystem location of the binding's mountpoint. + * + * @param binding A binding location + * @return The resolved path or null, if the binding cannot be found + */ + public String mount(String componentId, String binding, Path outdir) + throws BWFLAException, IOException, IllegalArgumentException { + if (binding == null || binding.isEmpty()) + throw new IllegalArgumentException("Binding is null or empty!"); + + if (binding.startsWith("rom://")) + binding = "rom-" + binding.substring("rom://".length()); + + if (binding.startsWith("binding://")) + binding = binding.substring("binding://".length()); + + // if (binding.contains("/")) + // throw new BWFLAException("Subresource bindings are currently not supported!"); + + // Let's see if we already have the resource mounted + String resourcePath = this.lookup(binding); + if (resourcePath != null) + return resourcePath; + + log.info("Resolving resource '" + binding + "'..."); + + final String realBindingId = binding; + final Binding resource = bindings.get(realBindingId); + if (resource == null) + throw new BWFLAException("Could not find binding for resource " + binding); + + log.info("Mounting binding '" + binding + "'..."); + + prepareResourceBinding(componentId, resource); + + // TODO: we need to resolve the full path here or earlier to + // ensure that all access options use the same path: + if (binding.startsWith("rom-")) // old rom bindings do not have COPY access by default + { + log.info("guessing resource is a ROM. force COPY access if not QCOW"); + + // check the file type first. + ImageInformation inf = new ImageInformation(resource.getUrl(), log); + ImageInformation.QemuImageFormat fmt = inf.getFileFormat(); + if (fmt != ImageInformation.QemuImageFormat.QCOW2) + resource.setAccess(Binding.AccessType.COPY); + } + + final MountOptions mountOpts = new MountOptions(); + if (resource.getFileSize() > 0) + mountOpts.setSize(resource.getFileSize()); + + Path imgPath = null; + ImageMounter.Mount mount = null; + switch (resource.getAccess()) { + case COW: + imgPath = outdir.resolve(realBindingId + ".cow"); + + QcowOptions qcowOptions = new QcowOptions(); + qcowOptions.setBackingFile(resource.getUrl()); + + EmulatorUtils.createCowFile(imgPath, qcowOptions); + + Path rawImagePath = outdir.resolve(realBindingId + ".dd"); + mount = imageMounter.mount(imgPath, rawImagePath, mountOpts); + + this.put(realBindingId, EntryType.RAW_MOUNT, resourcePath); + resourcePath = mount.getMountPoint().toAbsolutePath().toString(); + break; + case COPY: + imgPath = outdir.resolve(realBindingId + ".copy"); + EmulatorUtils.copyRemoteUrl(resource, imgPath, log); + resourcePath = imgPath.toString(); + break; + } + this.put(realBindingId, EntryType.IMAGE, imgPath.toAbsolutePath().toString()); + + // resourcePath is now the base path for the binding we want to find + if (resourcePath == null || !(new File(resourcePath).canRead())) { + final String message = "Binding target at location " + + resource.getUrl() + " cannot be accessed!"; + + throw new BWFLAException(message); + } + + // Is the raw file a block device with a filesystem? + final String fsType = this.getImageFileSystem(resource); + if (mount != null && fsType != null && !fsType.isEmpty()) { + final Path mountpoint = outdir.resolve(realBindingId + "." + fsType.replace(',', '.') + ".fuse"); + imageMounter.mount(mount, mountpoint, FileSystemType.fromString(fsType)); + resourcePath = mountpoint.toString(); + this.put(realBindingId, EntryType.FS_MOUNT, resourcePath); + } + + // Is local alias specified? + final String alias = resource.getLocalAlias(); + if (alias != null) { + final Path link = outdir.resolve(alias); + Files.deleteIfExists(link); + Files.createSymbolicLink(link, Paths.get(resourcePath)); + resourcePath = link.toString(); + + this.put(realBindingId, EntryType.ALIAS, resourcePath); + } + + this.add(realBindingId, resourcePath); + return resourcePath; + } + + /** + * Unmounts all registered bindings + */ + public void cleanup() { + log.info("Unmounting bindings..."); + { + final Set idsToRemove = new HashSet(); + + final String[] fuseIdSuffixes = new String[]{ + ID_SEPARATOR + EntryType.RAW_MOUNT.value, + ID_SEPARATOR + EntryType.FS_MOUNT.value, + }; + + paths.forEach((id, path) -> { + for (String suffix : fuseIdSuffixes) { + if (!id.endsWith(suffix)) + continue; // Not a FUSE-mount! + + final int end = id.length() - suffix.length(); + idsToRemove.add(id.substring(0, end)); + } + }); + + imageMounter.unmount(); + idsToRemove.forEach((id) -> this.remove(id)); + idsToRemove.clear(); + } + } + + public static String toBindingId(String base, EntryType type) { + return (base + ID_SEPARATOR + type.value); + } + + + /* =============== Internal Helpers =============== */ + + private void put(String id, Binding binding) { + bindings.put(id, binding); + + log.info("Added binding: " + id); + } + + private void put(String base, EntryType type, String path) { + final String id = BindingsManager.toBindingId(base, type); + paths.put(id, path); + + log.info("Added binding's path entry: " + id + " -> " + path); + } + + private void add(String id, String path) { + paths.put(id, path); + + log.info("Added binding's access path: " + id + " -> " + path); + } + + private void remove(String id) { + bindings.remove(id); + paths.remove(id); + for (EntryType type : EntryType.values()) + paths.remove(BindingsManager.toBindingId(id, type)); + } + + private String getImageFileSystem(Binding resource) { + String fstype = null; + + if (resource instanceof ImageArchiveBinding) { + final ImageArchiveBinding image = (ImageArchiveBinding) resource; + fstype = image.getFileSystemType(); + } else if (resource instanceof BlobStoreBinding) { + final BlobStoreBinding image = (BlobStoreBinding) resource; + if (image.getFileSystemType() != null && image.getMountFS()) + fstype = image.getFileSystemType().toString(); + } + + if (fstype != null) + fstype = fstype.toLowerCase(); + + return fstype; + } } diff --git a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java new file mode 100644 index 0000000..8a33642 --- /dev/null +++ b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java @@ -0,0 +1,96 @@ +package de.bwl.bwfla.emucomp.components; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.bwl.bwfla.emucomp.common.ComponentConfiguration; +import de.bwl.bwfla.emucomp.common.FileCollection; +import de.bwl.bwfla.emucomp.common.FileCollectionEntry; +import de.bwl.bwfla.emucomp.common.MachineConfiguration; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Slf4j +public class BindingsResolver { + + public static String providedConfigurationPath; + private static StringBuffer extractedConfigurationHolder; + private static ComponentConfiguration extractedComponentConfiguration; + + private static final ThreadLocal mapperThreadLocal = ThreadLocal.withInitial(ObjectMapper::new); + + public static Optional findFileCollectionDeclaration(String objectArchive, String id) { + tryExtractConfiguration(); + + if (extractedComponentConfiguration instanceof MachineConfiguration) { + MachineConfiguration configuration = ((MachineConfiguration) extractedComponentConfiguration); + + if (configuration.getAbstractDataResource() != null) { + Set collect = configuration.getAttachedFiles() + .stream() + .filter(e -> e.getArchive().equals(id)) + .filter(e -> e.getArchive().equals(objectArchive)) + .collect(Collectors.toSet()); + + return collect.stream().findFirst(); + } + } + return Optional.empty(); + } + + public static synchronized void tryExtractConfiguration() { + if (extractedConfigurationHolder == null && (providedConfigurationPath == null || providedConfigurationPath.isEmpty())) { + try { + URL configDirUrl = BindingsResolver.class.getResource("/config"); + + if (configDirUrl == null) { + throw new IOException("Configuration directory not found on classpath: /config"); + } + + URI configDirUri = configDirUrl.toURI(); + File configDir = new File(configDirUri); + + if (!configDir.exists() || !configDir.isDirectory()) { + throw new IOException("Resolved path is not a directory: " + configDir.getAbsolutePath()); + } + + File[] jsonFiles = configDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); + + if (jsonFiles == null || jsonFiles.length == 0) { + throw new IOException("No JSON files found in directory: " + configDir.getAbsolutePath()); + } + + if (jsonFiles[0] != null) { + providedConfigurationPath = jsonFiles[0].getAbsolutePath(); + String resourcePath = "/config" + jsonFiles[0].getName(); + try (InputStream is = BindingsResolver.class.getResourceAsStream("/" + resourcePath)) { + if (is == null) { + throw new FileNotFoundException("Resource not found after selection: " + resourcePath); + } + String data = new String(is.readAllBytes(), StandardCharsets.UTF_8); + extractedConfigurationHolder = new StringBuffer(data); + + extractedComponentConfiguration = mapperThreadLocal.get().readValue(data, ComponentConfiguration.class); + } + } else { + throw new IOException("No suitable JSON file could be chosen from: " + configDir.getAbsolutePath()); + } + + } catch (URISyntaxException | IOException e) { + log.error("Failed to extract configuration from classpath directory: /config, " + e.getMessage()); + + } catch (Exception e) { + log.error("An error occurred during configuration processing: " + e.getMessage()); + } + } + } +} diff --git a/emucomp-impl/src/main/resources/config/test-machine.json b/emucomp-impl/src/main/resources/config/test-machine.json index 7a3a5d4..442cf29 100755 --- a/emucomp-impl/src/main/resources/config/test-machine.json +++ b/emucomp-impl/src/main/resources/config/test-machine.json @@ -1,103 +1,131 @@ { "type": "emulationEnvironment", - "arch" : "x86_64", - "emulator" : { - "bean" : "Qemu", - "containerName" : "qemu-system" + "arch": "x86_64", + "emulator": { + "bean": "Qemu", + "containerName": "qemu-system" }, - "ui_options" : { - "html5" : { - "pointer_lock" : false + "ui_options": { + "html5": { + "pointer_lock": false }, - "input" : { - "clientKbdLayout" : "us", - "required" : false + "input": { + "clientKbdLayout": "us", + "required": false }, - "audio_system" : "webRTC", - "disableGhostCursor" : false + "audio_system": "webRTC", + "disableGhostCursor": false }, - "operatingSystemId" : "os:linux:ubuntu", - "drive" : [ { - "unit" : "0", - "type" : "floppy", - "filesystem" : "fat12", - "boot" : false, - "plugged" : false - }, { - "unit" : "1", - "type" : "floppy", - "filesystem" : "fat12", - "boot" : false, - "plugged" : false - }, { - "data" : "binding://", - "iface" : "ide", - "bus" : "0", - "unit" : "0", - "type" : "disk", - "boot" : true, - "plugged" : false - }, { - "data" : "binding:///", - "iface" : "ide", - "bus" : "0", - "unit" : "1", - "type" : "cdrom", - "filesystem" : "ISO", - "boot" : false, - "plugged" : false - }, { - "iface" : "ide", - "bus" : "1", - "unit" : "0", - "type" : "disk", - "boot" : false, - "plugged" : false - }, { - "iface" : "ide", - "bus" : "1", - "unit" : "1", - "type" : "cdrom", - "filesystem" : "ISO", - "boot" : false, - "plugged" : false - } ], - "nic" : [ { - "hwaddress" : "c6:7c:66:b0:21:d5" - } ], - "abstractDataResource" : [ { - "imageArchiveBinding" : { - "imageId" : "", - "type" : "user", - "backendName" : "default", - "filesize" : -1, - "id" : "emucon-rootfs" + "operatingSystemId": "os:linux:ubuntu", + "drive": [ + { + "unit": "0", + "type": "floppy", + "filesystem": "fat12", + "boot": false, + "plugged": false + }, + { + "unit": "1", + "type": "floppy", + "filesystem": "fat12", + "boot": false, + "plugged": false + }, + { + "data": "binding://", + "iface": "ide", + "bus": "0", + "unit": "0", + "type": "disk", + "boot": true, + "plugged": false + }, + { + "data": "binding:///", + "iface": "ide", + "bus": "0", + "unit": "1", + "type": "cdrom", + "filesystem": "ISO", + "boot": false, + "plugged": false + }, + { + "iface": "ide", + "bus": "1", + "unit": "0", + "type": "disk", + "boot": false, + "plugged": false + }, + { + "iface": "ide", + "bus": "1", + "unit": "1", + "type": "cdrom", + "filesystem": "ISO", + "boot": false, + "plugged": false + } + ], + "nic": [ + { + "hwaddress": "c6:7c:66:b0:21:d5" } - }, { - "imageArchiveBinding" : { - "imageId" : "", - "type" : "user", - "backendName" : "default", - "filesize" : -1, - "id" : "" + ], + "abstractDataResource": [ + { + "imageArchiveBinding": { + "imageId": "", + "type": "user", + "backendName": "default", + "filesize": -1, + "id": "emucon-rootfs" + } + }, + { + "imageArchiveBinding": { + "imageId": "", + "type": "user", + "backendName": "default", + "filesize": -1, + "id": "" + } + }, + { + "objectArchiveBinding": { + "objectId": "", + "archive": "default", + "id": "" + } } - },{ - "objectArchiveBinding" : { - "objectId" : "", - "archive" : "default", - "id" : "" + ], + "attachedFiles": [ + { + "id": "", + "label": "file-label", + "files": [ + { + "type": "cdrom", + "resourceType": "disk", + "isDefault": true, + "archive": "default", + "objectId": "" + } + ] } - } ], - "nativeConfig" : { - "value" : "-vga cirrus -smp 1 -net nic,model=rtl8139 -soundhw ac97 -m 1024 -usb -usbdevice tablet" + ], + "nativeConfig": { + "value": "-vga cirrus -smp 1 -net nic,model=rtl8139 -soundhw ac97 -m 1024 -usb -usbdevice tablet" }, - "isLinuxRuntime" : false, - "description" : { - "title" : "test environment" + "isLinuxRuntime": false, + "description": { + "title": "test environment" }, - "configurationType" : "MachineConfigurationTemplate", - "metaDataVersion" : "1", - "deleted" : false, - "id" : "test-machine", - "timestamp" : "2023-09-05T17:46:29.483217Z" + "configurationType": "MachineConfigurationTemplate", + "metaDataVersion": "1", + "deleted": false, + "id": "test-machine", + "timestamp": "2023-09-05T17:46:29.483217Z" } \ No newline at end of file From 2339fa3b48cce8983339ac67e798bdc3c4651144 Mon Sep 17 00:00:00 2001 From: peeqle Date: Mon, 16 Jun 2025 13:57:34 +0300 Subject: [PATCH 2/5] fix: build scope --- Dockerfile | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 03ed60e..04c45a3 100755 --- a/Dockerfile +++ b/Dockerfile @@ -10,21 +10,24 @@ COPY ./emucomp-api ./emucomp-api/ RUN mvn clean package -DskipTests -FROM eclipse-temurin:11-jdk-jammy +FROM ubuntu:22.04 AS tools_builder +WORKDIR /tmp/tools-build COPY build-emucon-tools.sh . RUN chmod +x build-emucon-tools.sh +RUN ./build-emucon-tools.sh + +FROM eclipse-temurin:11-jdk-jammy + RUN echo "locales locales/default_environment_locale string en_US.UTF-8" | debconf-set-selections && \ echo "keyboard-configuration keyboard-configuration/layoutcode string us" | debconf-set-selections && \ DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ xpra socat vde2 qemu-utils qemu-system ntfs-3g util-linux sudo unzip \ - && rm -rf /var/lib/apt/lists/* \ - -RUN ./build-emucon-tools.sh + && rm -rf /var/lib/apt/lists/* RUN mkdir -p /linapple-pie /minivmac /usr/local/bin -COPY --from=eaas-emucon-tools /emucon-output /usr/local/bin/ +COPY --from=tools_builder /tmp/tools-build/emucon-output /usr/local/bin/ WORKDIR /app COPY --from=build /app/emucomp-api/target/quarkus-app/quarkus-run.jar ./quarkus-run.jar @@ -33,4 +36,4 @@ COPY --from=build /app/emucomp-api/target/quarkus-app/app/ ./app/ COPY --from=build /app/emucomp-api/target/quarkus-app/quarkus/ ./quarkus/ EXPOSE 8080 -CMD ["java", "-jar", "./quarkus-run.jar"] +CMD ["java", "-jar", "./quarkus-run.jar"] \ No newline at end of file From 3851baa69dd98d732831b5f7f0de70daf63caccc Mon Sep 17 00:00:00 2001 From: peeqle Date: Wed, 18 Jun 2025 14:40:57 +0300 Subject: [PATCH 3/5] feat: Static loading of ImageArchiveBinding configuration thread-safe loading --- Dockerfile | 15 +- build-emucon-tools.sh | 70 - build.sh | 8 + .../emucomp/components/BindingsResolver.java | 82 +- .../components/emulators/EmulatorBean.java | 4207 ++++++++--------- emucon-tools/README.md | 45 + emucon-tools/bootstrap.sh | 20 + .../commands/layer/layers/base/Dockerfile | 10 + .../layer/layers/base/data/bwfla.list | 1 + .../layer/layers/base/data/pin-bwfla.pref | 4 + .../commands/layer/layers/base/data/xpra.list | 1 + .../builder/commands/layer/layers/base/run.sh | 140 + .../layers/base/scripts/dependencies.txt | 13 + .../layer/layers/base/scripts/emucon-init | 140 + .../layer/layers/base/scripts/emulators.txt | 21 + .../layer/layers/base/scripts/install.sh | 91 + .../commands/layer/layers/emulator/run.sh | 194 + .../layer/layers/emulator/scripts/install.sh | 40 + emucon-tools/builder/commands/layer/run.sh | 57 + emucon-tools/builder/commands/tool/run.sh | 100 + .../tool/scripts/build-image-tools.sh | 21 + .../commands/tool/scripts/build-runc.sh | 18 + .../tool/scripts/build-runtime-tools.sh | 21 + .../builder/commands/tool/scripts/build.sh | 32 + .../commands/tool/scripts/prepare-go.sh | 19 + emucon-tools/builder/emucon-build | 57 + emucon-tools/install.sh | 103 + emucon-tools/installer/install-deps.sh | 13 + emucon-tools/installer/install-oci-tools.sh | 96 + emucon-tools/installer/install-scripts.sh | 129 + emucon-tools/runtime/lib/emucon/helpers.sh | 175 + .../runtime/lib/emucon/qcow-create.sh | 87 + emucon-tools/runtime/lib/emucon/qcow-mkfs.sh | 88 + emucon-tools/runtime/lib/emucon/qcow-mount.sh | 133 + .../runtime/lib/emucon/qcow-unmount.sh | 103 + .../runtime/share/emucon/sudoers.template | 48 + 36 files changed, 4137 insertions(+), 2265 deletions(-) delete mode 100644 build-emucon-tools.sh create mode 100644 build.sh create mode 100644 emucon-tools/README.md create mode 100755 emucon-tools/bootstrap.sh create mode 100644 emucon-tools/builder/commands/layer/layers/base/Dockerfile create mode 100644 emucon-tools/builder/commands/layer/layers/base/data/bwfla.list create mode 100644 emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref create mode 100644 emucon-tools/builder/commands/layer/layers/base/data/xpra.list create mode 100755 emucon-tools/builder/commands/layer/layers/base/run.sh create mode 100644 emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt create mode 100644 emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init create mode 100644 emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt create mode 100755 emucon-tools/builder/commands/layer/layers/base/scripts/install.sh create mode 100755 emucon-tools/builder/commands/layer/layers/emulator/run.sh create mode 100755 emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh create mode 100755 emucon-tools/builder/commands/layer/run.sh create mode 100755 emucon-tools/builder/commands/tool/run.sh create mode 100644 emucon-tools/builder/commands/tool/scripts/build-image-tools.sh create mode 100644 emucon-tools/builder/commands/tool/scripts/build-runc.sh create mode 100644 emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh create mode 100755 emucon-tools/builder/commands/tool/scripts/build.sh create mode 100644 emucon-tools/builder/commands/tool/scripts/prepare-go.sh create mode 100755 emucon-tools/builder/emucon-build create mode 100755 emucon-tools/install.sh create mode 100755 emucon-tools/installer/install-deps.sh create mode 100755 emucon-tools/installer/install-oci-tools.sh create mode 100755 emucon-tools/installer/install-scripts.sh create mode 100644 emucon-tools/runtime/lib/emucon/helpers.sh create mode 100644 emucon-tools/runtime/lib/emucon/qcow-create.sh create mode 100644 emucon-tools/runtime/lib/emucon/qcow-mkfs.sh create mode 100644 emucon-tools/runtime/lib/emucon/qcow-mount.sh create mode 100644 emucon-tools/runtime/lib/emucon/qcow-unmount.sh create mode 100644 emucon-tools/runtime/share/emucon/sudoers.template diff --git a/Dockerfile b/Dockerfile index 04c45a3..7ac8fd5 100755 --- a/Dockerfile +++ b/Dockerfile @@ -10,24 +10,21 @@ COPY ./emucomp-api ./emucomp-api/ RUN mvn clean package -DskipTests -FROM ubuntu:22.04 AS tools_builder - -WORKDIR /tmp/tools-build -COPY build-emucon-tools.sh . -RUN chmod +x build-emucon-tools.sh - -RUN ./build-emucon-tools.sh +COPY emucon-tools . +COPY /tmp/oci-tools /usr/local +RUN /emucon-tools/installer/install-scripts.sh --destination /usr/local \ + && /emucon-tools/installer/install-deps.sh FROM eclipse-temurin:11-jdk-jammy RUN echo "locales locales/default_environment_locale string en_US.UTF-8" | debconf-set-selections && \ echo "keyboard-configuration keyboard-configuration/layoutcode string us" | debconf-set-selections && \ DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \ - xpra socat vde2 qemu-utils qemu-system ntfs-3g util-linux sudo unzip \ + xpra socat vde2 qemu-utils qemu-system ntfs-3g util-linux \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /linapple-pie /minivmac /usr/local/bin -COPY --from=tools_builder /tmp/tools-build/emucon-output /usr/local/bin/ +COPY /tmp/tools-build/emucon-output /usr/local/bin/ WORKDIR /app COPY --from=build /app/emucomp-api/target/quarkus-app/quarkus-run.jar ./quarkus-run.jar diff --git a/build-emucon-tools.sh b/build-emucon-tools.sh deleted file mode 100644 index 4429f3c..0000000 --- a/build-emucon-tools.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -IMAGE_NAME="eaas-emucon-tools" -EMUCON_TOOLS_URL="https://gitlab.com/emulation-as-a-service/emucon-tools/-/archive/master/emucon-tools-master.zip" -TARGET_DIR="unzip" - -cleanup() { - if [ -f "$ZIP_FILE" ]; then - rm -f "$ZIP_FILE" - echo "Deleted temporary ZIP file: $ZIP_FILE" - fi - - if [ -d "$TARGET_DIR" ]; then - rm -rf "$TARGET_DIR" - echo "Deleted temporary directory: $TARGET_DIR" - fi -} -trap cleanup EXIT - -for cmd in curl unzip; do - if ! command -v "$cmd" &> /dev/null; then - echo "Error: Command '$cmd' not found. Please install it." - exit 1 - fi -done - -if docker images --format "{{.Repository}}" | grep -q "^${IMAGE_NAME}$"; then - echo "Image '$IMAGE_NAME' found locally." - exit 0 -else - echo "Image '$IMAGE_NAME' not found locally. Downloading from ${}" - ZIP_FILE=$(basename "$EMUCON_TOOLS_URL") - echo "Downloading $ZIP_URL to $ZIP_FILE..." - if ! curl -L -o "$ZIP_FILE" "$ZIP_URL"; then - echo "Error: Failed to download $ZIP_URL." - exit 1 - fi - echo "Download complete." - - mkdir -p "$TARGET_DIR" || { echo "Error: Failed to create directory '$TARGET_DIR'."; exit 1; } - echo "Created directory: '$TARGET_DIR'" - - if ! unzip -q "$ZIP_FILE" -d "$TARGET_DIR"; then - echo "Error: Failed to unzip $ZIP_FILE to $TARGET_DIR." - exit 1 - fi - - cd "$TARGET_DIR/common/eaas-emucon-tools" - if [ $? -eq 0 ]; then - docker build -t ${IMAGE_NAME}:latest . - BUILD_STATUS=$? - - if [ "$BUILD_STATUS" -eq 0 ]; then - echo "-------------------------------------" - echo "Docker image build SUCCESS!" - echo "Image '${IMAGE_NAME}:latest' created successfully." - echo "You can now run it using: docker run -it ${IMAGE_NAME}:latest" - echo "-------------------------------------" - exit 0 - else - echo "-------------------------------------" - echo "Docker image build FAILED with exit code: $BUILD_STATUS" - echo "Please review the build logs above for errors." - echo "-------------------------------------" - exit 1 - fi - else - echo "Error: Failed to change directory to '$TARGET_DIR/common/eaas-emucon-tools'." - exit 1 - fi -fi \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..d1acb25 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + + +apt-get update && apt-get install -y curl unzip sudo \ + && rm -rf /var/lib/apt/lists/* + +mkdir /tmp/oci-tools +./emucon-tools/installer/install-oci-tools.sh --destination /tmp/oci-tools \ No newline at end of file diff --git a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java index 8a33642..33d228d 100644 --- a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java +++ b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/BindingsResolver.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import de.bwl.bwfla.emucomp.common.ComponentConfiguration; import de.bwl.bwfla.emucomp.common.FileCollection; -import de.bwl.bwfla.emucomp.common.FileCollectionEntry; +import de.bwl.bwfla.emucomp.common.ImageArchiveBinding; import de.bwl.bwfla.emucomp.common.MachineConfiguration; import lombok.extern.slf4j.Slf4j; @@ -22,7 +22,9 @@ @Slf4j public class BindingsResolver { - public static String providedConfigurationPath; + private static final String _classpath_DEFAULT_CONFIGURATION_PATH = "/config"; + + public static volatile String providedConfigurationPath; private static StringBuffer extractedConfigurationHolder; private static ComponentConfiguration extractedComponentConfiguration; @@ -37,7 +39,7 @@ public static Optional findFileCollectionDeclaration(String obje if (configuration.getAbstractDataResource() != null) { Set collect = configuration.getAttachedFiles() .stream() - .filter(e -> e.getArchive().equals(id)) + .filter(e -> e.getId().equals(id)) .filter(e -> e.getArchive().equals(objectArchive)) .collect(Collectors.toSet()); @@ -47,13 +49,37 @@ public static Optional findFileCollectionDeclaration(String obje return Optional.empty(); } + public static Optional findFirstImageArchiveBindingDeclaration() { + tryExtractConfiguration(); + + if (extractedComponentConfiguration instanceof MachineConfiguration) { + MachineConfiguration configuration = ((MachineConfiguration) extractedComponentConfiguration); + + if (configuration.getAbstractDataResource() != null) { + Set collect = configuration.getAbstractDataResource() + .stream() + .filter(e -> e instanceof ImageArchiveBinding) + .map(e -> (ImageArchiveBinding) e) + .collect(Collectors.toSet()); + if (collect.isEmpty()) { + log.info("Cannot find any ImageArchiveBinding configuration at {}", providedConfigurationPath); + return Optional.empty(); + } + + log.info("Found {} ImageArchiveBinding declarations, returning {}", collect.size(), collect.stream().findFirst().get().getImageId()); + return collect.stream().findFirst(); + } + } + return Optional.empty(); + } + public static synchronized void tryExtractConfiguration() { if (extractedConfigurationHolder == null && (providedConfigurationPath == null || providedConfigurationPath.isEmpty())) { try { - URL configDirUrl = BindingsResolver.class.getResource("/config"); + URL configDirUrl = BindingsResolver.class.getResource(_classpath_DEFAULT_CONFIGURATION_PATH); if (configDirUrl == null) { - throw new IOException("Configuration directory not found on classpath: /config"); + throw new IOException("Configuration directory not found on classpath: " + _classpath_DEFAULT_CONFIGURATION_PATH); } URI configDirUri = configDirUrl.toURI(); @@ -71,26 +97,46 @@ public static synchronized void tryExtractConfiguration() { if (jsonFiles[0] != null) { providedConfigurationPath = jsonFiles[0].getAbsolutePath(); - String resourcePath = "/config" + jsonFiles[0].getName(); - try (InputStream is = BindingsResolver.class.getResourceAsStream("/" + resourcePath)) { - if (is == null) { - throw new FileNotFoundException("Resource not found after selection: " + resourcePath); - } - String data = new String(is.readAllBytes(), StandardCharsets.UTF_8); - extractedConfigurationHolder = new StringBuffer(data); - - extractedComponentConfiguration = mapperThreadLocal.get().readValue(data, ComponentConfiguration.class); - } + loadConfiguration(_classpath_DEFAULT_CONFIGURATION_PATH + jsonFiles[0].getName()); } else { throw new IOException("No suitable JSON file could be chosen from: " + configDir.getAbsolutePath()); } - + return; } catch (URISyntaxException | IOException e) { - log.error("Failed to extract configuration from classpath directory: /config, " + e.getMessage()); - + log.error("Failed to extract configuration from classpath directory: {}, " + e.getMessage(), _classpath_DEFAULT_CONFIGURATION_PATH); } catch (Exception e) { log.error("An error occurred during configuration processing: " + e.getMessage()); } } + + File file = new File(providedConfigurationPath); + if (file.isDirectory()) { + try { + File[] jsonFiles = file.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); + + if (jsonFiles == null || jsonFiles.length == 0) { + throw new IOException("No JSON files found in directory: " + file.getAbsolutePath()); + } + loadConfiguration(jsonFiles[0].getAbsolutePath()); + } catch (IOException e) { + log.error("Failed to extract configuration from classpath directory: {}, " + e.getMessage(), file.getAbsolutePath()); + } + } else { + loadConfiguration(providedConfigurationPath); + } + } + + private static void loadConfiguration(String path) { + try (InputStream is = BindingsResolver.class.getResourceAsStream(path)) { + if (is == null) { + throw new FileNotFoundException("Resource not found after selection: " + path); + } + String data = new String(is.readAllBytes(), StandardCharsets.UTF_8); + extractedConfigurationHolder = new StringBuffer(data); + + extractedComponentConfiguration = mapperThreadLocal.get().readValue(data, ComponentConfiguration.class); + } catch (IOException e) { + log.error("Cannot load configuration from {}", path); + } } } diff --git a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/emulators/EmulatorBean.java b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/emulators/EmulatorBean.java index 796c0c0..b0f0df3 100755 --- a/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/emulators/EmulatorBean.java +++ b/emucomp-impl/src/main/java/de/bwl/bwfla/emucomp/components/emulators/EmulatorBean.java @@ -21,7 +21,6 @@ import com.openslx.eaas.common.util.RuncStateInformation; -import de.bwl.bwfla.emucomp.components.api.EmulatorComponent; import de.bwl.bwfla.emucomp.common.*; import de.bwl.bwfla.emucomp.common.EmulatorUtils.XmountOutputFormat; import de.bwl.bwfla.emucomp.common.datatypes.EmuCompState; @@ -39,8 +38,10 @@ import de.bwl.bwfla.emucomp.common.services.guacplay.record.SessionRecorder; import de.bwl.bwfla.emucomp.common.utils.*; import de.bwl.bwfla.emucomp.components.BindingsManager; +import de.bwl.bwfla.emucomp.components.BindingsResolver; import de.bwl.bwfla.emucomp.components.EaasComponentBean; import de.bwl.bwfla.emucomp.components.Tail; +import de.bwl.bwfla.emucomp.components.api.EmulatorComponent; import de.bwl.bwfla.emucomp.components.emulators.IpcDefs.EventID; import de.bwl.bwfla.emucomp.components.emulators.IpcDefs.MessageType; import de.bwl.bwfla.emucomp.control.IPCWebsocketProxy; @@ -50,7 +51,6 @@ import de.bwl.bwfla.emucomp.xpra.IAudioStreamer; import de.bwl.bwfla.emucomp.xpra.PulseAudioStreamer; import org.apache.commons.io.FileUtils; - import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.glyptodon.guacamole.GuacamoleException; @@ -85,8 +85,7 @@ /** * @author iv1004 */ -public abstract class EmulatorBean extends EaasComponentBean implements EmulatorComponent -{ +public abstract class EmulatorBean extends EaasComponentBean implements EmulatorComponent { private EmulatorBeanMode emuBeanMode; @Inject @@ -97,872 +96,847 @@ public abstract class EmulatorBean extends EaasComponentBean implements Emulator @ConfigProperty(name = "emucomp.alsa_card") public String alsa_card; - @Inject - @ConfigProperty(name = "emucomp.libfaketime") - public String libfaketime; - - // allow beans to disable the fake clock preload - protected boolean disableFakeClock = false; - - private boolean isPulseAudioEnabled = false; - - @Inject - protected ThreadFactory workerThreadFactory; - - @Inject - @Named("managed-executor") - protected ExecutorService executor; - - @Inject - @ConfigProperty(name = "rest.blobstore") - protected String blobStoreRestAddress; + @Inject + @ConfigProperty(name = "emucomp.libfaketime") + public String libfaketime; - private final String containerOutput = "container-output"; + // allow beans to disable the fake clock preload + protected boolean disableFakeClock = false; - private static final String emulatorDataBase = "/home/bwfla/server-data/emulator-data/"; - - protected final TunnelConfig tunnelConfig = new TunnelConfig(); + private boolean isPulseAudioEnabled = false; - protected final EmulatorBeanState emuBeanState = new EmulatorBeanState(EmuCompState.EMULATOR_UNDEFINED, LOG); + @Inject + protected ThreadFactory workerThreadFactory; - protected MachineConfiguration emuEnvironment; - private String emuNativeConfig; - protected final Map containers = Collections.synchronizedMap(new HashMap()); + @Inject + @Named("managed-executor") + protected ExecutorService executor; - protected final ProcessRunner emuRunner = new ProcessRunner(); - protected final ArrayList vdeProcesses = new ArrayList(); + @Inject + @ConfigProperty(name = "rest.blobstore") + protected String blobStoreRestAddress; - protected final BindingsManager bindings = new BindingsManager(LOG); + private final String containerOutput = "container-output"; - protected String protocol; + private static final String emulatorDataBase = "/home/bwfla/server-data/emulator-data/"; - /** Emulator's configuration settings */ - protected final EmulatorConfig emuConfig = new EmulatorConfig(); + protected final TunnelConfig tunnelConfig = new TunnelConfig(); - /* IPC for control messages */ - private IpcSocket ctlSocket = null; - protected IpcMessageWriter ctlMsgWriter = null; - protected IpcMessageReader ctlMsgReader = null; - private IpcMessageQueue ctlMsgQueue = new IpcMessageQueue(); - private IpcEventSet ctlEvents = new IpcEventSet(); - protected String emuCtlSocketName = null; + protected final EmulatorBeanState emuBeanState = new EmulatorBeanState(EmuCompState.EMULATOR_UNDEFINED, LOG); - /** Is a client attached to the emulator? */ - private final AtomicBoolean isClientAttachedFlag = new AtomicBoolean(false); + protected MachineConfiguration emuEnvironment; + private String emuNativeConfig; + protected final Map containers = Collections.synchronizedMap(new HashMap()); - /* Session recording + replay members */ - private SessionRecorder recorder = null; - private SessionPlayerWrapper player = null; + protected final ProcessRunner emuRunner = new ProcessRunner(); + protected final ArrayList vdeProcesses = new ArrayList(); - /** Tool for capturing of screenshots. */ - private ScreenShooter scrshooter = null; - - final boolean isScreenshotEnabled = ConfigProvider.getConfig().getValue("emucomp.enable_screenshooter", Boolean.class); + protected final BindingsManager bindings = new BindingsManager(LOG); - protected PostScriptPrinter printer = null; + protected String protocol; - /** Internal chain of IGuacInterceptors. */ - private final GuacInterceptorChain interceptors = new GuacInterceptorChain(2); + /** + * Emulator's configuration settings + */ + protected final EmulatorConfig emuConfig = new EmulatorConfig(); - /** Number of unprocessed messages, before message-processors start to block. */ - private static final int MESSAGE_BUFFER_CAPACITY = 4096; + /* IPC for control messages */ + private IpcSocket ctlSocket = null; + protected IpcMessageWriter ctlMsgWriter = null; + protected IpcMessageReader ctlMsgReader = null; + private IpcMessageQueue ctlMsgQueue = new IpcMessageQueue(); + private IpcEventSet ctlEvents = new IpcEventSet(); + protected String emuCtlSocketName = null; - /** Filename for temporary trace-files. */ - private static final String TRACE_FILE = "session" + GuacDefs.TRACE_FILE_EXT; + /** + * Is a client attached to the emulator? + */ + private final AtomicBoolean isClientAttachedFlag = new AtomicBoolean(false); - /* Supported protocol names */ - private static final String PROTOCOL_SDLONP = "sdlonp"; - private static final String PROTOCOL_Y11 = "y11"; + /* Session recording + replay members */ + private SessionRecorder recorder = null; + private SessionPlayerWrapper player = null; - /* Supported audio driver names */ - private static final String AUDIODRIVER_PULSE = "pulse"; + /** + * Tool for capturing of screenshots. + */ + private ScreenShooter scrshooter = null; - /** Data directory inside of the emulator-containers */ - private static final String EMUCON_DATA_DIR = "/emucon/data"; + final boolean isScreenshotEnabled = ConfigProvider.getConfig().getValue("emucomp.enable_screenshooter", Boolean.class); - /** Binding ID for container's root filesystem */ - private static final String EMUCON_ROOTFS_BINDING_ID = "emucon-rootfs"; + protected PostScriptPrinter printer = null; - private static final String EMULATOR_DEFAULT_ARCHIVE = "emulators"; + /** + * Internal chain of IGuacInterceptors. + */ + private final GuacInterceptorChain interceptors = new GuacInterceptorChain(2); - @Inject - @ConfigProperty(name = "components.emulator_containers.enabled") - protected boolean emuContainerModeEnabled = true; + /** + * Number of unprocessed messages, before message-processors start to block. + */ + private static final int MESSAGE_BUFFER_CAPACITY = 4096; - @Inject - @ConfigProperty(name = "components.emulator_containers.uid") - protected String emuContainerUserId = null; + /** + * Filename for temporary trace-files. + */ + private static final String TRACE_FILE = "session" + GuacDefs.TRACE_FILE_EXT; - @Inject - @ConfigProperty(name = "components.emulator_containers.gid") - protected String emuContainerGroupId = null; + /* Supported protocol names */ + private static final String PROTOCOL_SDLONP = "sdlonp"; + private static final String PROTOCOL_Y11 = "y11"; - /** Files to include into a container-checkpoint */ - protected List emuContainerFilesToCheckpoint = new ArrayList<>(); + /* Supported audio driver names */ + private static final String AUDIODRIVER_PULSE = "pulse"; - /** File extension for checkpoints */ - private static final String CHECKPOINT_FILE_EXTENSION = ".tar.gz"; + /** + * Data directory inside of the emulator-containers + */ + private static final String EMUCON_DATA_DIR = "/emucon/data"; - protected boolean isKvmDeviceEnabled = false; + /** + * Binding ID for container's root filesystem + */ + private static final String EMUCON_ROOTFS_BINDING_ID = "emucon-rootfs"; + private static final String EMULATOR_DEFAULT_ARCHIVE = "emulators"; - public static EmulatorBean createEmulatorBean(MachineConfiguration env) throws ClassNotFoundException - { - String targetBean = env.getEmulator().getBean() + "Bean"; - Class beanClass = Class.forName(EmulatorBean.class.getPackage().getName() + "." + targetBean); - return (EmulatorBean)CDI.current().select(beanClass).get(); - } + @Inject + @ConfigProperty(name = "components.emulator_containers.enabled") + protected boolean emuContainerModeEnabled = true; - public boolean isClientAttached() - { - return isClientAttachedFlag.get(); - } + @Inject + @ConfigProperty(name = "components.emulator_containers.uid") + protected String emuContainerUserId = null; - public boolean isSdlBackendEnabled() - { - return (emuBeanMode == EmulatorBeanMode.SDLONP); - } + @Inject + @ConfigProperty(name = "components.emulator_containers.gid") + protected String emuContainerGroupId = null; + + /** + * Files to include into a container-checkpoint + */ + protected List emuContainerFilesToCheckpoint = new ArrayList<>(); + + /** + * File extension for checkpoints + */ + private static final String CHECKPOINT_FILE_EXTENSION = ".tar.gz"; + + protected boolean isKvmDeviceEnabled = false; + + + public static EmulatorBean createEmulatorBean(MachineConfiguration env) throws ClassNotFoundException { + String targetBean = env.getEmulator().getBean() + "Bean"; + Class beanClass = Class.forName(EmulatorBean.class.getPackage().getName() + "." + targetBean); + return (EmulatorBean) CDI.current().select(beanClass).get(); + } + + public boolean isClientAttached() { + return isClientAttachedFlag.get(); + } + + public boolean isSdlBackendEnabled() { + return (emuBeanMode == EmulatorBeanMode.SDLONP); + } + + public boolean isXpraBackendEnabled() { + return (emuBeanMode == EmulatorBeanMode.XPRA); + } + + public boolean isPulseAudioEnabled() { + return isPulseAudioEnabled; + } + + public boolean isLocalModeEnabled() { + return (emuBeanMode == EmulatorBeanMode.Y11); + } + + public boolean isHeadlessModeEnabled() { + return (emuBeanMode == EmulatorBeanMode.HEADLESS); + } + + public boolean isContainerModeEnabled() { + return emuContainerModeEnabled; + } + + public EmulatorBeanMode getEmuBeanMode() { + return emuBeanMode; + } + + public int getInactivityTimeout() { + return inactivityTimeout; + } + + @Override + public String getComponentType() { + return "machine"; + } + + boolean isHeadlessSupported() { + return false; + } + + boolean isBeanReady() { + return true; // this is the default, if the bean has no internal state + } + + @Override + public ComponentState getState() throws BWFLAException { + switch (this.getEmulatorState()) { + case EMULATOR_RUNNING: + return (this.isBeanReady()) ? ComponentState.RUNNING : ComponentState.INITIALIZING; + case EMULATOR_INACTIVE: + return ComponentState.INACTIVE; + case EMULATOR_STOPPED: + return ComponentState.STOPPED; + case EMULATOR_FAILED: + return ComponentState.FAILED; + default: + return ComponentState.INITIALIZING; + } + } + + public EmuCompState getEmulatorState() { + final boolean isEmulatorInactive = ctlEvents.poll(EventID.CLIENT_INACTIVE); + synchronized (emuBeanState) { + if (isEmulatorInactive) + emuBeanState.set(EmuCompState.EMULATOR_INACTIVE); + return emuBeanState.get(); + } + } + + public String getContainerId() { + final String compid = this.getComponentId(); + return compid.substring(1 + compid.lastIndexOf("+")); + } + + public String getContainerUserId() { + return emuContainerUserId; + } + + public String getContainerGroupId() { + return emuContainerGroupId; + } + + + public Function getContainerHostPathReplacer() { + final String hostDataDir = this.getDataDir().toString(); + final String hostBindingsDir = this.getBindingsDir().toString(); + return (cmdarg) -> { + return cmdarg.replaceAll(hostBindingsDir, EMUCON_DATA_DIR + "/bindings") + .replaceAll(hostDataDir, EMUCON_DATA_DIR); + }; + } + + public Path getDataDir() { + final Path workdir = this.getWorkingDir(); + if (this.isContainerModeEnabled()) + return workdir.resolve("data"); + + return workdir; + } + + public Path getNetworksDir() { + return this.getDataDir().resolve("networks"); + } + + public Path getSocketsDir() { + return this.getDataDir().resolve("sockets"); + } + + public Path getUploadsDir() { + return this.getDataDir().resolve("uploads"); + } + + public Path getStateDir() { + return this.getWorkingDir().resolve("state"); + } + + public Path getPrinterDir() { + return this.getDataDir().resolve("printer"); + } + + private void createWorkingSubDirs() throws IOException { + // Currently, working directory in container-mode is structured as follows: + // + // / + // state/ -> Container's memory dump + // data/ -> Session/emulator specific data + // networks/ -> Networking files + // sockets/ -> IO + CTRL sockets + // uploads/ -> Uploaded files + + // If container-mode is disabled: / == data/ + + Files.createDirectories(this.getDataDir()); + Files.createDirectories(this.getNetworksDir()); + Files.createDirectories(this.getSocketsDir()); + Files.createDirectories(this.getUploadsDir()); + Files.createDirectories(this.getPrinterDir()); + } + + private Path getXpraSocketPath() { + return this.getSocketsDir().resolve("xpra-iosocket"); + } + + private Path getPulseAudioSocketPath() { + return this.getSocketsDir().resolve("pulse-iosocket"); + } + + /** + * Returns emulator's runtime layer name. + */ + protected String getEmuContainerName(MachineConfiguration machineConfiguration) { + final String message = this.getClass().getSimpleName() + + " does not support container-mode!"; + + throw new UnsupportedOperationException(message); + } + + private String getEmulatorArchive() { + /* + Removed due to the migration to static resource declaration + */ +// String archive = ConfigProvider.getConfig().getValue("emucomp.emulator_archive", String.class); +// if(archive == null || archive.isEmpty()) +// return EMULATOR_DEFAULT_ARCHIVE; +// return archive; + + return EMULATOR_DEFAULT_ARCHIVE; + } + + public void initialize(ComponentConfiguration compConfig) throws BWFLAException { + synchronized (emuBeanState) { + final EmuCompState curstate = emuBeanState.get(); + if (curstate != EmuCompState.EMULATOR_UNDEFINED) { + String message = "Cannot initialize EmulatorBean!"; + throw new IllegalEmulatorStateException(message, curstate) + .setId(this.getComponentId()); + } - public boolean isXpraBackendEnabled() - { - return (emuBeanMode == EmulatorBeanMode.XPRA); - } + emuBeanState.set(EmuCompState.EMULATOR_BUSY); + } - public boolean isPulseAudioEnabled() - { - return isPulseAudioEnabled; - } - - public boolean isLocalModeEnabled() - { - return (emuBeanMode == EmulatorBeanMode.Y11); - } - - public boolean isHeadlessModeEnabled() { return (emuBeanMode == EmulatorBeanMode.HEADLESS); } - - public boolean isContainerModeEnabled() - { - return emuContainerModeEnabled; - } - - public EmulatorBeanMode getEmuBeanMode() - { - return emuBeanMode; - } - - public int getInactivityTimeout() - { - return inactivityTimeout; - } - - @Override - public String getComponentType() - { - return "machine"; - } - - boolean isHeadlessSupported() { return false; } - - boolean isBeanReady() { - return true; // this is the default, if the bean has no internal state - } - - @Override - public ComponentState getState() throws BWFLAException - { - switch (this.getEmulatorState()) { - case EMULATOR_RUNNING: - return (this.isBeanReady()) ? ComponentState.RUNNING : ComponentState.INITIALIZING; - case EMULATOR_INACTIVE: - return ComponentState.INACTIVE; - case EMULATOR_STOPPED: - return ComponentState.STOPPED; - case EMULATOR_FAILED: - return ComponentState.FAILED; - default: - return ComponentState.INITIALIZING; - } - } - - public EmuCompState getEmulatorState() - { - final boolean isEmulatorInactive = ctlEvents.poll(EventID.CLIENT_INACTIVE); - synchronized (emuBeanState) { - if (isEmulatorInactive) - emuBeanState.set(EmuCompState.EMULATOR_INACTIVE); - return emuBeanState.get(); - } - } - - public String getContainerId() - { - final String compid = this.getComponentId(); - return compid.substring(1 + compid.lastIndexOf("+")); - } - - public String getContainerUserId() - { - return emuContainerUserId; - } - - public String getContainerGroupId() - { - return emuContainerGroupId; - } - - - public Function getContainerHostPathReplacer() - { - final String hostDataDir = this.getDataDir().toString(); - final String hostBindingsDir = this.getBindingsDir().toString(); - return (cmdarg) -> { - return cmdarg.replaceAll(hostBindingsDir, EMUCON_DATA_DIR + "/bindings") - .replaceAll(hostDataDir, EMUCON_DATA_DIR); - }; - } - - public Path getDataDir() - { - final Path workdir = this.getWorkingDir(); - if (this.isContainerModeEnabled()) - return workdir.resolve("data"); - - return workdir; - } - - public Path getNetworksDir() - { - return this.getDataDir().resolve("networks"); - } - - public Path getSocketsDir() - { - return this.getDataDir().resolve("sockets"); - } - - public Path getUploadsDir() - { - return this.getDataDir().resolve("uploads"); - } - - public Path getStateDir() - { - return this.getWorkingDir().resolve("state"); - } - - public Path getPrinterDir() - { - return this.getDataDir().resolve("printer"); - } - - private void createWorkingSubDirs() throws IOException - { - // Currently, working directory in container-mode is structured as follows: - // - // / - // state/ -> Container's memory dump - // data/ -> Session/emulator specific data - // networks/ -> Networking files - // sockets/ -> IO + CTRL sockets - // uploads/ -> Uploaded files - - // If container-mode is disabled: / == data/ - - Files.createDirectories(this.getDataDir()); - Files.createDirectories(this.getNetworksDir()); - Files.createDirectories(this.getSocketsDir()); - Files.createDirectories(this.getUploadsDir()); - Files.createDirectories(this.getPrinterDir()); - } - - private Path getXpraSocketPath() - { - return this.getSocketsDir().resolve("xpra-iosocket"); - } - - private Path getPulseAudioSocketPath() - { - return this.getSocketsDir().resolve("pulse-iosocket"); - } - - /** Returns emulator's runtime layer name. */ - protected String getEmuContainerName(MachineConfiguration machineConfiguration) - { - final String message = this.getClass().getSimpleName() - + " does not support container-mode!"; - - throw new UnsupportedOperationException(message); - } - - private String getEmulatorArchive() { - String archive = ConfigProvider.getConfig().getValue("emucomp.emulator_archive", String.class); - if(archive == null || archive.isEmpty()) - return EMULATOR_DEFAULT_ARCHIVE; - return archive; - } - - public void initialize(ComponentConfiguration compConfig) throws BWFLAException - { - synchronized (emuBeanState) { - final EmuCompState curstate = emuBeanState.get(); - if (curstate != EmuCompState.EMULATOR_UNDEFINED) { - String message = "Cannot initialize EmulatorBean!"; - throw new IllegalEmulatorStateException(message, curstate) - .setId(this.getComponentId()); - } + final MachineConfiguration env = (MachineConfiguration) compConfig; + emuBeanMode = getEmuBeanMode(env); + emuRunner.setLogger(LOG); - emuBeanState.set(EmuCompState.EMULATOR_BUSY); - } - - final MachineConfiguration env = (MachineConfiguration) compConfig; - emuBeanMode = getEmuBeanMode(env); - emuRunner.setLogger(LOG); + try { + this.createWorkingSubDirs(); + } catch (IOException error) { + throw this.newInitFailureException("Creating working subdirs failed!", error); + } - try { - this.createWorkingSubDirs(); - } - catch (IOException error) { - throw this.newInitFailureException("Creating working subdirs failed!", error); - } + if (this.isSdlBackendEnabled()) { + // Create control sockets + try { + ctlSocket = IpcSocket.create(this.newCtlSocketName("srv"), IpcSocket.Type.DGRAM, true); + ctlMsgWriter = new IpcMessageWriter(ctlSocket); + ctlMsgReader = new IpcMessageReader(ctlSocket); + emuCtlSocketName = this.newCtlSocketName("emu"); + } catch (Throwable exception) { + throw this.newInitFailureException("Constructing control sockets failed!", exception); + } - if (this.isSdlBackendEnabled()) { - // Create control sockets - try { - ctlSocket = IpcSocket.create(this.newCtlSocketName("srv"), IpcSocket.Type.DGRAM, true); - ctlMsgWriter = new IpcMessageWriter(ctlSocket); - ctlMsgReader = new IpcMessageReader(ctlSocket); - emuCtlSocketName = this.newCtlSocketName("emu"); - } - catch (Throwable exception) { - throw this.newInitFailureException("Constructing control sockets failed!", exception); - } + // Prepare configuration for tunnels + tunnelConfig.setGuacdHostname("127.0.0.1"); + tunnelConfig.setGuacdPort(TunnelConfig.GUACD_PORT); + tunnelConfig.setInterceptor(interceptors); + } - // Prepare configuration for tunnels - tunnelConfig.setGuacdHostname("127.0.0.1"); - tunnelConfig.setGuacdPort(TunnelConfig.GUACD_PORT); - tunnelConfig.setInterceptor(interceptors); - } + emuConfig.setHardTermination(false); - emuConfig.setHardTermination(false); + try { + if (this.isContainerModeEnabled()) { + // Check, that rootfs-image is specified! + final boolean isRootFsFound = env.getAbstractDataResource().stream() + .anyMatch((resource) -> resource.getId().contentEquals(EMUCON_ROOTFS_BINDING_ID)); - try - { - if (this.isContainerModeEnabled()) { - // Check, that rootfs-image is specified! - final boolean isRootFsFound = env.getAbstractDataResource().stream() - .anyMatch((resource) -> resource.getId().contentEquals(EMUCON_ROOTFS_BINDING_ID)); - - if (!isRootFsFound) { - // Not found, try to get latest image ID as configured by the archive - final var image = this.findEmulatorImage(env); - env.getAbstractDataResource().add(image); - } - } + if (!isRootFsFound) { + // Not found, try to get latest image ID as configured by the archive + final var image = this.findEmulatorImage(); + env.getAbstractDataResource().add(image); + } + } - this.setRuntimeConfiguration(env); - } - catch (Throwable error) { - throw this.newInitFailureException("Initializing runtime configuration failed!", error); - } + this.setRuntimeConfiguration(env); + } catch (Throwable error) { + throw this.newInitFailureException("Initializing runtime configuration failed!", error); + } - LOG.info("Emulation session initialized in " + emuBeanMode.name() + " mode."); - LOG.info("Working directory created at: " + this.getWorkingDir()); - emuBeanState.update(EmuCompState.EMULATOR_READY); - } + LOG.info("Emulation session initialized in " + emuBeanMode.name() + " mode."); + LOG.info("Working directory created at: " + this.getWorkingDir()); + emuBeanState.update(EmuCompState.EMULATOR_READY); + } - private void unmountBindings() - { - bindings.cleanup(); - } + private void unmountBindings() { + bindings.cleanup(); + } - synchronized public void destroy() - { - synchronized (emuBeanState) - { - final EmuCompState curstate = emuBeanState.get(); - if (curstate == EmuCompState.EMULATOR_UNDEFINED) - return; + synchronized public void destroy() { + synchronized (emuBeanState) { + final EmuCompState curstate = emuBeanState.get(); + if (curstate == EmuCompState.EMULATOR_UNDEFINED) + return; - if (curstate == EmuCompState.EMULATOR_BUSY) { - LOG.severe("Destroying EmulatorBean while other operation is in-flight!"); - return; - } + if (curstate == EmuCompState.EMULATOR_BUSY) { + LOG.severe("Destroying EmulatorBean while other operation is in-flight!"); + return; + } - emuBeanState.set(EmuCompState.EMULATOR_UNDEFINED); - } - this.stopInternal(); + emuBeanState.set(EmuCompState.EMULATOR_UNDEFINED); + } + this.stopInternal(); - // free container IDs and remove corresp. files - for(File container: containers.values()) - container.delete(); + // free container IDs and remove corresp. files + for (File container : containers.values()) + container.delete(); - containers.clear(); + containers.clear(); - // kill vde networking threads - for(ProcessRunner subprocess : this.vdeProcesses) - { - if(subprocess.isProcessRunning()) - subprocess.stop(); + // kill vde networking threads + for (ProcessRunner subprocess : this.vdeProcesses) { + if (subprocess.isProcessRunning()) + subprocess.stop(); - subprocess.cleanup(); - } + subprocess.cleanup(); + } - this.unmountBindings(); + this.unmountBindings(); - // Stop screenshot-tool - if (scrshooter != null) - scrshooter.finish(); + // Stop screenshot-tool + if (scrshooter != null) + scrshooter.finish(); - // Stop and finalize session-recording - if (recorder != null && !recorder.isFinished()) - { - try { - recorder.finish(); - } - catch (IOException e) { - LOG.log(Level.SEVERE, e.getMessage(), e); - } - } + // Stop and finalize session-recording + if (recorder != null && !recorder.isFinished()) { + try { + recorder.finish(); + } catch (IOException e) { + LOG.log(Level.SEVERE, e.getMessage(), e); + } + } - // Cleanup the control sockets - try { - if (ctlSocket != null) - ctlSocket.close(); - } - catch (IOException exception) { - LOG.log(Level.SEVERE, exception.getMessage(), exception); - } + // Cleanup the control sockets + try { + if (ctlSocket != null) + ctlSocket.close(); + } catch (IOException exception) { + LOG.log(Level.SEVERE, exception.getMessage(), exception); + } - // Cleanup emulator's runner here - if (emuRunner.isProcessValid()) { - emuRunner.printStdOut(); - emuRunner.printStdErr(); - } + // Cleanup emulator's runner here + if (emuRunner.isProcessValid()) { + emuRunner.printStdOut(); + emuRunner.printStdErr(); + } - emuRunner.cleanup(); + emuRunner.cleanup(); - LOG.info("EmulatorBean destroyed."); + LOG.info("EmulatorBean destroyed."); - // Destroy base class! - super.destroy(); - } + // Destroy base class! + super.destroy(); + } - @Override - public void start() throws BWFLAException - { - synchronized (emuBeanState) { - final EmuCompState curstate = emuBeanState.get(); - if (curstate != EmuCompState.EMULATOR_READY && curstate != EmuCompState.EMULATOR_STOPPED) { - throw new BWFLAException("Cannot start emulator! Wrong state detected: " + curstate.value()) - .setId(this.getComponentId()); + @Override + public void start() throws BWFLAException { + synchronized (emuBeanState) { + final EmuCompState curstate = emuBeanState.get(); + if (curstate != EmuCompState.EMULATOR_READY && curstate != EmuCompState.EMULATOR_STOPPED) { + throw new BWFLAException("Cannot start emulator! Wrong state detected: " + curstate.value()) + .setId(this.getComponentId()); - } - emuBeanState.set(EmuCompState.EMULATOR_BUSY); - } + } + emuBeanState.set(EmuCompState.EMULATOR_BUSY); + } - try { - if (this.isSdlBackendEnabled() || this.isXpraBackendEnabled() || this.isHeadlessModeEnabled()) - this.startBackend(); - else { - throw new BWFLAException("Trying to start emulator using unimplemented mode: " + this.getEmuBeanMode()) - .setId(this.getComponentId()); - } - } - catch(Throwable error) { - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - String message = "Starting emulator failed!"; - LOG.log(Level.SEVERE, message, error); - if (error.getMessage() != null) - message += " " + error.getMessage(); - - throw new BWFLAException(message, error) - .setId(this.getComponentId()); - } - } + try { + if (this.isSdlBackendEnabled() || this.isXpraBackendEnabled() || this.isHeadlessModeEnabled()) + this.startBackend(); + else { + throw new BWFLAException("Trying to start emulator using unimplemented mode: " + this.getEmuBeanMode()) + .setId(this.getComponentId()); + } + } catch (Throwable error) { + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + String message = "Starting emulator failed!"; + LOG.log(Level.SEVERE, message, error); + if (error.getMessage() != null) + message += " " + error.getMessage(); + + throw new BWFLAException(message, error) + .setId(this.getComponentId()); + } + } - private void startBackend() throws BWFLAException, IOException - { - if (this.isLocalModeEnabled()) - LOG.info("Local-mode enabled. Emulator will be started locally!"); + private void startBackend() throws BWFLAException, IOException { + if (this.isLocalModeEnabled()) + LOG.info("Local-mode enabled. Emulator will be started locally!"); - if(!disableFakeClock) { - LOG.info("initializing fake clock"); - emuRunner.addEnvVariable("LD_PRELOAD", "/usr/local/lib/LD_PRELOAD_clock_gettime.so"); - } - if (this.isXpraBackendEnabled()) { - // TODO: implement this, if needed! - if (!this.isContainerModeEnabled()) { - throw new BWFLAException("Non-containerized XPRA sessions are not supported!") - .setId(this.getComponentId()); - } + if (!disableFakeClock) { + LOG.info("initializing fake clock"); + emuRunner.addEnvVariable("LD_PRELOAD", "/usr/local/lib/LD_PRELOAD_clock_gettime.so"); + } + if (this.isXpraBackendEnabled()) { + // TODO: implement this, if needed! + if (!this.isContainerModeEnabled()) { + throw new BWFLAException("Non-containerized XPRA sessions are not supported!") + .setId(this.getComponentId()); + } - final boolean isGpuEnabled = ConfigProvider.getConfig() - .getValue("components.xpra.enable_gpu", Boolean.class); + final boolean isGpuEnabled = ConfigProvider.getConfig() + .getValue("components.xpra.enable_gpu", Boolean.class); - if (isGpuEnabled) { - emuRunner.getCommand() - .add(0, "vglrun"); - } - } + if (isGpuEnabled) { + emuRunner.getCommand() + .add(0, "vglrun"); + } + } - String xprasockInContainer = null; + String xprasockInContainer = null; - if (this.isContainerModeEnabled()) { - LOG.info("Container-mode enabled. Emulator will be started inside of a container!"); + if (this.isContainerModeEnabled()) { + LOG.info("Container-mode enabled. Emulator will be started inside of a container!"); - final String cid = this.getContainerId(); - final String workdir = this.getWorkingDir().toString(); - final String rootfsdir = this.lookupResource(EMUCON_ROOTFS_BINDING_ID); + final String cid = this.getContainerId(); + final String workdir = this.getWorkingDir().toString(); + final String rootfsdir = this.lookupResource(EMUCON_ROOTFS_BINDING_ID); - // Generate container's config - { - final String conConfigPath = Paths.get(workdir, "config.json").toString(); - - final ProcessRunner cgen = new ProcessRunner(); - cgen.setCommand("emucon-cgen"); - cgen.addArguments("--output", conConfigPath); - cgen.addArguments("--user-id", emuContainerUserId); - cgen.addArguments("--group-id", emuContainerGroupId); - cgen.addArguments("--rootfs", rootfsdir); - - if (getEmulatorWorkdir() != null) { - cgen.addArguments("--workdir", getEmulatorWorkdir()); - } - - final String hostDataDir = this.getDataDir().toString(); - - final Function hostPathReplacer = this.getContainerHostPathReplacer(); - - // Mount emulator's data dir entries - try (Stream entries = Files.list(this.getDataDir())) { - entries.forEach((entry) -> { - final String path = entry.toString(); - cgen.addArgument("--mount"); - cgen.addArgument(path, ":", hostPathReplacer.apply(path), ":bind:rw"); - - if(path.contains("printer")) // compatibility for old snapshots - { - String target = hostPathReplacer.apply(path).replace("printer", "print"); - cgen.addArgument("--mount"); - cgen.addArgument(path, ":", target, ":bind:rw"); - } - }); - } - catch (Exception error) { - LOG.log(Level.WARNING, "Listing '" + hostDataDir + "' failed!\n", error); - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - return; - } - - final List bindingIdsToSkip = new ArrayList<>(); - bindingIdsToSkip.add(EMUCON_ROOTFS_BINDING_ID); - if (emuEnvironment.hasCheckpointBindingId()) - bindingIdsToSkip.add(emuEnvironment.getCheckpointBindingId()); - - // Mount fuse-mounted bindings separately - bindings.paths().forEach((entry) -> { - final String curid = entry.getKey(); - for (String idToSkip : bindingIdsToSkip) { - if (curid.contentEquals(idToSkip)) - return; // Skip it! - } + // Generate container's config + { + final String conConfigPath = Paths.get(workdir, "config.json").toString(); - final String path = entry.getValue(); - cgen.addArgument("--mount"); - cgen.addArgument(path, ":", hostPathReplacer.apply(path), ":bind:rw"); - }); - - // Add emulator's env-vars with replaced host data directory - emuRunner.getEnvVariables() - .forEach((name, value) -> { - cgen.addArgument("--env"); - cgen.addArgument(name); - if (value != null && !value.isEmpty()) - cgen.addArgValues("=", hostPathReplacer.apply(value)); - }); - - // Enable KVM device (if needed) - if (isKvmDeviceEnabled) - cgen.addArgument("--enable-kvm"); - - final String conNetDir = hostPathReplacer.apply(this.getNetworksDir().toString()); - - // Add emulator's command with replaced host data directory - cgen.addArguments("--", "/usr/bin/emucon-init", "--networks-dir", conNetDir); - if (this.isXpraBackendEnabled()) { - final String xprasock = this.getXpraSocketPath().toString(); - xprasockInContainer = hostPathReplacer.apply(xprasock); - cgen.addArguments("--xpra-socket", xprasockInContainer); - } - - if (this.isPulseAudioEnabled()) { - final String pulsesock = this.getPulseAudioSocketPath().toString(); - cgen.addArguments("--pulse-socket", hostPathReplacer.apply(pulsesock)); - } - - cgen.addArgument("--"); - emuRunner.getCommand() - .forEach((cmdarg) -> cgen.addArgument(hostPathReplacer.apply(cmdarg))); - - cgen.setLogger(LOG); - if (!cgen.execute()) { - LOG.warning("Generating container's config failed!"); - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - return; - } - - if (this.isSdlBackendEnabled()) { - // Replace host data directory in emulator's config - emuConfig.setIoSocket(hostPathReplacer.apply(emuConfig.getIoSocket())); - } - } + final ProcessRunner cgen = new ProcessRunner(); + cgen.setCommand("emucon-cgen"); + cgen.addArguments("--output", conConfigPath); + cgen.addArguments("--user-id", emuContainerUserId); + cgen.addArguments("--group-id", emuContainerGroupId); + cgen.addArguments("--rootfs", rootfsdir); - emuRunner.cleanup(); - - // Replace host's emulator command line... - emuRunner.setCommand("emucon-run"); - emuRunner.addArguments("--non-interactive"); - emuRunner.addArguments("--container-id", cid); - emuRunner.addArguments("--working-dir", workdir); - emuRunner.addArguments("--rootfs-type", "tree"); - emuRunner.addArguments("--rootfs-dir", rootfsdir); - if (emuEnvironment.hasCheckpointBindingId()) { - try { - final String checkpointBindingId = emuEnvironment.getCheckpointBindingId(); - final String checkpoint = this.lookupResource(checkpointBindingId); - emuRunner.addArguments("--checkpoint", checkpoint); - - LOG.info("Container state will be restored from checkpoint"); - } - catch (Exception error) { - throw new BWFLAException("Looking up checkpoint image failed!", error) - .setId(this.getComponentId()); - } - } - } + if (getEmulatorWorkdir() != null) { + cgen.addArguments("--workdir", getEmulatorWorkdir()); + } - emuRunner.redirectStdErrToStdOut(true); + final String hostDataDir = this.getDataDir().toString(); + + final Function hostPathReplacer = this.getContainerHostPathReplacer(); + + // Mount emulator's data dir entries + try (Stream entries = Files.list(this.getDataDir())) { + entries.forEach((entry) -> { + final String path = entry.toString(); + cgen.addArgument("--mount"); + cgen.addArgument(path, ":", hostPathReplacer.apply(path), ":bind:rw"); + + if (path.contains("printer")) // compatibility for old snapshots + { + String target = hostPathReplacer.apply(path).replace("printer", "print"); + cgen.addArgument("--mount"); + cgen.addArgument(path, ":", target, ":bind:rw"); + } + }); + } catch (Exception error) { + LOG.log(Level.WARNING, "Listing '" + hostDataDir + "' failed!\n", error); + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + return; + } - if (!emuRunner.start()) { - throw new BWFLAException("Starting emulator failed!") - .setId(this.getComponentId()); - } + final List bindingIdsToSkip = new ArrayList<>(); + bindingIdsToSkip.add(EMUCON_ROOTFS_BINDING_ID); + if (emuEnvironment.hasCheckpointBindingId()) + bindingIdsToSkip.add(emuEnvironment.getCheckpointBindingId()); + + // Mount fuse-mounted bindings separately + bindings.paths().forEach((entry) -> { + final String curid = entry.getKey(); + for (String idToSkip : bindingIdsToSkip) { + if (curid.contentEquals(idToSkip)) + return; // Skip it! + } + + final String path = entry.getValue(); + cgen.addArgument("--mount"); + cgen.addArgument(path, ":", hostPathReplacer.apply(path), ":bind:rw"); + }); + + // Add emulator's env-vars with replaced host data directory + emuRunner.getEnvVariables() + .forEach((name, value) -> { + cgen.addArgument("--env"); + cgen.addArgument(name); + if (value != null && !value.isEmpty()) + cgen.addArgValues("=", hostPathReplacer.apply(value)); + }); + + // Enable KVM device (if needed) + if (isKvmDeviceEnabled) + cgen.addArgument("--enable-kvm"); + + final String conNetDir = hostPathReplacer.apply(this.getNetworksDir().toString()); + + // Add emulator's command with replaced host data directory + cgen.addArguments("--", "/usr/bin/emucon-init", "--networks-dir", conNetDir); + if (this.isXpraBackendEnabled()) { + final String xprasock = this.getXpraSocketPath().toString(); + xprasockInContainer = hostPathReplacer.apply(xprasock); + cgen.addArguments("--xpra-socket", xprasockInContainer); + } - if (this.isXpraBackendEnabled()) { - if (emuEnvironment.hasCheckpointBindingId()) { - this.waitUntilPathExists(this.getXpraSocketPath(), EmuCompState.EMULATOR_BUSY); - this.waitUntilRestoreDone(); - } - else { - final String rootfs = bindings.lookup(BindingsManager.toBindingId(EMUCON_ROOTFS_BINDING_ID, BindingsManager.EntryType.FS_MOUNT)); - final Path path = Paths.get(rootfs, "tmp", "xpra-started"); - this.waitUntilPathExists(path, EmuCompState.EMULATOR_BUSY); - this.waitUntilPathExists(this.getXpraSocketPath(), EmuCompState.EMULATOR_BUSY); - } - RuncStateInformation info = RuncStateInformation.getRuncStateInformationForComponent(this.getComponentId()); - IPCWebsocketProxy.wait(Path.of(xprasockInContainer), Path.of("/proc", info.getPid())); - } - else if (this.isSdlBackendEnabled()) { - if (emuEnvironment.hasCheckpointBindingId()) { - // Wait for socket re-creation after resuming from a checkpoint - this.waitUntilEmulatorCtlSocketAvailable(EmuCompState.EMULATOR_BUSY); - } - else { - // Perform the following steps only, when starting a new emulator! - // Skip them, when resuming from a checkpoint. + if (this.isPulseAudioEnabled()) { + final String pulsesock = this.getPulseAudioSocketPath().toString(); + cgen.addArguments("--pulse-socket", hostPathReplacer.apply(pulsesock)); + } - this.waitUntilEmulatorCtlSocketReady(EmuCompState.EMULATOR_BUSY); + cgen.addArgument("--"); + emuRunner.getCommand() + .forEach((cmdarg) -> cgen.addArgument(hostPathReplacer.apply(cmdarg))); - if (!this.sendEmulatorConfig()) - return; + cgen.setLogger(LOG); + if (!cgen.execute()) { + LOG.warning("Generating container's config failed!"); + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + return; + } - if (!this.waitUntilEmulatorReady(EmuCompState.EMULATOR_BUSY)) - return; - } - } + if (this.isSdlBackendEnabled()) { + // Replace host data directory in emulator's config + emuConfig.setIoSocket(hostPathReplacer.apply(emuConfig.getIoSocket())); + } + } - if (this.isPulseAudioEnabled()) - this.waitUntilPathExists(this.getPulseAudioSocketPath(), EmuCompState.EMULATOR_BUSY); - - LOG.info("Emulator started in process " + emuRunner.getProcessId()); - - if (this.isSdlBackendEnabled()) { - final Thread ctlSockObserver = workerThreadFactory.newThread(() -> { - while (emuBeanState.fetch() != EmuCompState.EMULATOR_UNDEFINED) { - try { - // Try to receive new message - if (!ctlMsgReader.read(5000)) - continue; - - // Message could be read, queue it for further processing - if (ctlMsgReader.isNotification()) - ctlEvents.add(ctlMsgReader.getEventID()); - else { - final byte msgtype = ctlMsgReader.getMessageType(); - final byte[] msgdata = ctlMsgReader.getMessageData(); - ctlMsgQueue.put(msgtype, msgdata); - } - } - catch (Exception exception) { - if (emuBeanState.fetch() == EmuCompState.EMULATOR_UNDEFINED) - break; // Ignore problems when destroying session! + emuRunner.cleanup(); - LOG.warning("An error occured while reading from control-socket!"); - LOG.log(Level.SEVERE, exception.getMessage(), exception); - } - } - } - ); + // Replace host's emulator command line... + emuRunner.setCommand("emucon-run"); + emuRunner.addArguments("--non-interactive"); + emuRunner.addArguments("--container-id", cid); + emuRunner.addArguments("--working-dir", workdir); + emuRunner.addArguments("--rootfs-type", "tree"); + emuRunner.addArguments("--rootfs-dir", rootfsdir); + if (emuEnvironment.hasCheckpointBindingId()) { + try { + final String checkpointBindingId = emuEnvironment.getCheckpointBindingId(); + final String checkpoint = this.lookupResource(checkpointBindingId); + emuRunner.addArguments("--checkpoint", checkpoint); + + LOG.info("Container state will be restored from checkpoint"); + } catch (Exception error) { + throw new BWFLAException("Looking up checkpoint image failed!", error) + .setId(this.getComponentId()); + } + } + } - ctlSockObserver.start(); - } + emuRunner.redirectStdErrToStdOut(true); - final Thread emuObserver = workerThreadFactory.newThread(() -> { + if (!emuRunner.start()) { + throw new BWFLAException("Starting emulator failed!") + .setId(this.getComponentId()); + } - emuRunner.waitUntilFinished(); + if (this.isXpraBackendEnabled()) { + if (emuEnvironment.hasCheckpointBindingId()) { + this.waitUntilPathExists(this.getXpraSocketPath(), EmuCompState.EMULATOR_BUSY); + this.waitUntilRestoreDone(); + } else { + final String rootfs = bindings.lookup(BindingsManager.toBindingId(EMUCON_ROOTFS_BINDING_ID, BindingsManager.EntryType.FS_MOUNT)); + final Path path = Paths.get(rootfs, "tmp", "xpra-started"); + this.waitUntilPathExists(path, EmuCompState.EMULATOR_BUSY); + this.waitUntilPathExists(this.getXpraSocketPath(), EmuCompState.EMULATOR_BUSY); + } + RuncStateInformation info = RuncStateInformation.getRuncStateInformationForComponent(this.getComponentId()); + IPCWebsocketProxy.wait(Path.of(xprasockInContainer), Path.of("/proc", info.getPid())); + } else if (this.isSdlBackendEnabled()) { + if (emuEnvironment.hasCheckpointBindingId()) { + // Wait for socket re-creation after resuming from a checkpoint + this.waitUntilEmulatorCtlSocketAvailable(EmuCompState.EMULATOR_BUSY); + } else { + // Perform the following steps only, when starting a new emulator! + // Skip them, when resuming from a checkpoint. + + this.waitUntilEmulatorCtlSocketReady(EmuCompState.EMULATOR_BUSY); + + if (!this.sendEmulatorConfig()) + return; + + if (!this.waitUntilEmulatorReady(EmuCompState.EMULATOR_BUSY)) + return; + } + } - // upload emulator's output - if (this.isOutputAvailable()) { - LOG.info("Output is available!"); - try { - this.processEmulatorOutput(); - } - catch (BWFLAException error) { - LOG.log(Level.WARNING, "Processing emulator's output failed!", error); - } - } - else{ - LOG.info("No emulator output available!"); - } - - // cleanup will be performed later by EmulatorBean.destroy() - - synchronized (emuBeanState) { - if (EmulatorBean.this.isLocalModeEnabled()) { - // In local-mode emulator will be terminated by the user, - // without using our API. Set the correct state here! - emuBeanState.set(EmuCompState.EMULATOR_STOPPED); - } else { - final EmuCompState curstate = emuBeanState.get(); - if (curstate == EmuCompState.EMULATOR_RUNNING) { - LOG.warning("Emulator stopped unexpectedly!"); - // FIXME: setting here also to STOPPED, since there is currently no reliable way - // to determine (un-)successful termination depending on application exit code - emuBeanState.set(EmuCompState.EMULATOR_STOPPED); - } - } - } - }); + if (this.isPulseAudioEnabled()) + this.waitUntilPathExists(this.getPulseAudioSocketPath(), EmuCompState.EMULATOR_BUSY); + + LOG.info("Emulator started in process " + emuRunner.getProcessId()); + + if (this.isSdlBackendEnabled()) { + final Thread ctlSockObserver = workerThreadFactory.newThread(() -> { + while (emuBeanState.fetch() != EmuCompState.EMULATOR_UNDEFINED) { + try { + // Try to receive new message + if (!ctlMsgReader.read(5000)) + continue; + + // Message could be read, queue it for further processing + if (ctlMsgReader.isNotification()) + ctlEvents.add(ctlMsgReader.getEventID()); + else { + final byte msgtype = ctlMsgReader.getMessageType(); + final byte[] msgdata = ctlMsgReader.getMessageData(); + ctlMsgQueue.put(msgtype, msgdata); + } + } catch (Exception exception) { + if (emuBeanState.fetch() == EmuCompState.EMULATOR_UNDEFINED) + break; // Ignore problems when destroying session! + + LOG.warning("An error occured while reading from control-socket!"); + LOG.log(Level.SEVERE, exception.getMessage(), exception); + } + } + } + ); + + ctlSockObserver.start(); + } - emuObserver.start(); + final Thread emuObserver = workerThreadFactory.newThread(() -> { - if (printer != null) { - final Thread worker = workerThreadFactory.newThread(printer); - printer.setWorkerThread(worker); - worker.start(); - } + emuRunner.waitUntilFinished(); - if (this.isSdlBackendEnabled()) { - // Not in local mode? - if (!this.isLocalModeEnabled()) { - // Initialize the screenshot-tool - if (this.isScreenshotEnabled) { - scrshooter = new ScreenShooter(this.getComponentId(), 256); - scrshooter.prepare(); - - // Register the screenshot-tool - interceptors.addInterceptor(scrshooter); - } - } + // upload emulator's output + if (this.isOutputAvailable()) { + LOG.info("Output is available!"); + try { + this.processEmulatorOutput(); + } catch (BWFLAException error) { + LOG.log(Level.WARNING, "Processing emulator's output failed!", error); + } + } else { + LOG.info("No emulator output available!"); + } - // Prepare the connector for guacamole connections - { - final IThrowingSupplier clientTunnelCtor = () -> { - if (emuBeanState.fetch() == EmuCompState.EMULATOR_STOPPED) { - final var message = "Attaching client to stopped emulator failed!"; - LOG.warning(message); - throw new IllegalStateException(message); - } + // cleanup will be performed later by EmulatorBean.destroy() + + synchronized (emuBeanState) { + if (EmulatorBean.this.isLocalModeEnabled()) { + // In local-mode emulator will be terminated by the user, + // without using our API. Set the correct state here! + emuBeanState.set(EmuCompState.EMULATOR_STOPPED); + } else { + final EmuCompState curstate = emuBeanState.get(); + if (curstate == EmuCompState.EMULATOR_RUNNING) { + LOG.warning("Emulator stopped unexpectedly!"); + // FIXME: setting here also to STOPPED, since there is currently no reliable way + // to determine (un-)successful termination depending on application exit code + emuBeanState.set(EmuCompState.EMULATOR_STOPPED); + } + } + } + }); - final Runnable waitTask = () -> { - try { - EmulatorBean.this.attachClientToEmulator(); - EmulatorBean.this.waitForAttachedClient(); - } - catch (Exception exception) { - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - LOG.log(Level.SEVERE, "Attaching client to emulator failed!", exception); - } - }; + emuObserver.start(); - executor.execute(waitTask); + if (printer != null) { + final Thread worker = workerThreadFactory.newThread(printer); + printer.setWorkerThread(worker); + worker.start(); + } - // Construct the tunnel - final GuacTunnel tunnel = GuacTunnel.construct(tunnelConfig); - if (!this.isLocalModeEnabled() && (player != null)) - player.start(tunnel, this.getComponentId(), emuRunner.getProcessMonitor()); + if (this.isSdlBackendEnabled()) { + // Not in local mode? + if (!this.isLocalModeEnabled()) { + // Initialize the screenshot-tool + if (this.isScreenshotEnabled) { + scrshooter = new ScreenShooter(this.getComponentId(), 256); + scrshooter.prepare(); - return (player != null) ? player.getPlayerTunnel() : tunnel; - }; + // Register the screenshot-tool + interceptors.addInterceptor(scrshooter); + } + } - this.addControlConnector(new GuacamoleConnector(clientTunnelCtor, emuConfig.isRelativeMouse())); - } - } - else if (this.isXpraBackendEnabled()) { - this.addControlConnector(new XpraConnector(this.getXpraSocketPath())); - } + // Prepare the connector for guacamole connections + { + final IThrowingSupplier clientTunnelCtor = () -> { + if (emuBeanState.fetch() == EmuCompState.EMULATOR_STOPPED) { + final var message = "Attaching client to stopped emulator failed!"; + LOG.warning(message); + throw new IllegalStateException(message); + } + + final Runnable waitTask = () -> { + try { + EmulatorBean.this.attachClientToEmulator(); + EmulatorBean.this.waitForAttachedClient(); + } catch (Exception exception) { + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + LOG.log(Level.SEVERE, "Attaching client to emulator failed!", exception); + } + }; + + executor.execute(waitTask); + + // Construct the tunnel + final GuacTunnel tunnel = GuacTunnel.construct(tunnelConfig); + if (!this.isLocalModeEnabled() && (player != null)) + player.start(tunnel, this.getComponentId(), emuRunner.getProcessMonitor()); + + return (player != null) ? player.getPlayerTunnel() : tunnel; + }; + + this.addControlConnector(new GuacamoleConnector(clientTunnelCtor, emuConfig.isRelativeMouse())); + } + } else if (this.isXpraBackendEnabled()) { + this.addControlConnector(new XpraConnector(this.getXpraSocketPath())); + } - if (this.isPulseAudioEnabled()) { - final String cid = this.getComponentId(); - final Path pulsesock = this.getPulseAudioSocketPath(); - this.addControlConnector(new AudioConnector(() -> new PulseAudioStreamer(cid, pulsesock))); - } + if (this.isPulseAudioEnabled()) { + final String cid = this.getComponentId(); + final Path pulsesock = this.getPulseAudioSocketPath(); + this.addControlConnector(new AudioConnector(() -> new PulseAudioStreamer(cid, pulsesock))); + } - this.addControlConnector(new StdoutLogConnector(emuRunner.getStdOutPath())); - this.addControlConnector(new StderrLogConnector(emuRunner.getStdErrPath())); + this.addControlConnector(new StdoutLogConnector(emuRunner.getStdOutPath())); + this.addControlConnector(new StderrLogConnector(emuRunner.getStdErrPath())); - emuBeanState.update(EmuCompState.EMULATOR_RUNNING); - } + emuBeanState.update(EmuCompState.EMULATOR_RUNNING); + } - @Override - synchronized public String stop() throws BWFLAException - { - synchronized (emuBeanState) - { - final EmuCompState curstate = emuBeanState.get(); - if (curstate != EmuCompState.EMULATOR_RUNNING) { - LOG.warning("Cannot stop emulator! Wrong state detected: " + curstate.value()); - return this.getEmulatorOutputLocation(); - } + @Override + synchronized public String stop() throws BWFLAException { + synchronized (emuBeanState) { + final EmuCompState curstate = emuBeanState.get(); + if (curstate != EmuCompState.EMULATOR_RUNNING) { + LOG.warning("Cannot stop emulator! Wrong state detected: " + curstate.value()); + return this.getEmulatorOutputLocation(); + } - emuBeanState.set(EmuCompState.EMULATOR_BUSY); - } + emuBeanState.set(EmuCompState.EMULATOR_BUSY); + } - this.stopInternal(); + this.stopInternal(); - emuBeanState.update(EmuCompState.EMULATOR_STOPPED); - return this.getEmulatorOutputLocation(); - } + emuBeanState.update(EmuCompState.EMULATOR_STOPPED); + return this.getEmulatorOutputLocation(); + } - private boolean isOutputAvailable() - { - boolean hasOutput = (emuEnvironment.getOutputBindingId() != null && !emuEnvironment.getOutputBindingId().isEmpty()); - return hasOutput; - } + private boolean isOutputAvailable() { + boolean hasOutput = (emuEnvironment.getOutputBindingId() != null && !emuEnvironment.getOutputBindingId().isEmpty()); + return hasOutput; + } - //TODO when should this be used, when getChanged files? - private Path getAllFiles(BlobStoreBinding binding, Path cow, FileSystemType fsType) throws BWFLAException { - try (final ImageMounter mounter = new ImageMounter(LOG)) { - Path output; - if( binding.getResourceType() != Binding.ResourceType.ISO) { + //TODO when should this be used, when getChanged files? + private Path getAllFiles(BlobStoreBinding binding, Path cow, FileSystemType fsType) throws BWFLAException { + try (final ImageMounter mounter = new ImageMounter(LOG)) { + Path output; + if (binding.getResourceType() != Binding.ResourceType.ISO) { final Path workdir = this.getWorkingDir().resolve("output"); try { Files.createDirectories(workdir); - } - catch (IOException e) - { + } catch (IOException e) { throw new BWFLAException("failed creating workdir").setId(this.getComponentId()); } @@ -984,18 +958,17 @@ private Path getAllFiles(BlobStoreBinding binding, Path cow, FileSystemType fsTy Zip32Utils.zip(output.toFile(), srcdir.toFile(), exclude); } return output; + } else { + throw new BWFLAException("Unsupported binding type").setId(this.getComponentId()); } - else { - throw new BWFLAException("Unsupported binding type").setId(this.getComponentId()); - } - } - } + } + } - private Path getChangedFiles(Path cowImage) throws BWFLAException { - List partitionFiles = new ArrayList<>(); - QcowOptions qcowOptions = new QcowOptions(); - try (final ImageMounter mounter = new ImageMounter(LOG)) { - ImageInformation imageInformation = new ImageInformation(cowImage.toString(), LOG); + private Path getChangedFiles(Path cowImage) throws BWFLAException { + List partitionFiles = new ArrayList<>(); + QcowOptions qcowOptions = new QcowOptions(); + try (final ImageMounter mounter = new ImageMounter(LOG)) { + ImageInformation imageInformation = new ImageInformation(cowImage.toString(), LOG); String backingFileId = imageInformation.getBackingFile(); qcowOptions.setBackingFile(backingFileId); @@ -1009,123 +982,113 @@ private Path getChangedFiles(Path cowImage) throws BWFLAException { ImageMounter.Mount rawmnt = mounter.mount(lowerImgPath, workdir.resolve("lowerImageLayer.dd")); ImageMounter.Mount rawmnt2 = mounter.mount(cowImage, workdir.resolve("upperDir.dd")); - // load partition table - final DiskDescription disk = DiskDescription.read(Path.of(rawmnt.getTargetImage()), LOG); - if (!disk.hasPartitions()) - throw new BWFLAException("Disk seems to be not partitioned!"); + // load partition table + final DiskDescription disk = DiskDescription.read(Path.of(rawmnt.getTargetImage()), LOG); + if (!disk.hasPartitions()) + throw new BWFLAException("Disk seems to be not partitioned!"); - LOG.info("separating data by partition"); - for (DiskDescription.Partition partition : disk.getPartitions()) { - if (!partition.hasFileSystemType()) { - LOG.info("Partition " + partition.getIndex() + " is not formatted, skip"); - continue; - } - rawmnt = mounter.remount(rawmnt, partition.getStartOffset(), partition.getSize()); - rawmnt2 = mounter.remount(rawmnt2, partition.getStartOffset(), partition.getSize()); - - FileSystemType fsType; - try { - fsType = FileSystemType.fromString(partition.getFileSystemType()); - } - catch (IllegalArgumentException e) - { - LOG.warning("filesystem " + partition.getFileSystemType() + " not yet support. please report."); - continue; - } + LOG.info("separating data by partition"); + for (DiskDescription.Partition partition : disk.getPartitions()) { + if (!partition.hasFileSystemType()) { + LOG.info("Partition " + partition.getIndex() + " is not formatted, skip"); + continue; + } + rawmnt = mounter.remount(rawmnt, partition.getStartOffset(), partition.getSize()); + rawmnt2 = mounter.remount(rawmnt2, partition.getStartOffset(), partition.getSize()); - final ImageMounter.Mount fsmnt = mounter.mount(rawmnt, workdir.resolve("backingFileId.fs"), fsType); - final Path lowerDir = fsmnt.getMountPoint(); + FileSystemType fsType; + try { + fsType = FileSystemType.fromString(partition.getFileSystemType()); + } catch (IllegalArgumentException e) { + LOG.warning("filesystem " + partition.getFileSystemType() + " not yet support. please report."); + continue; + } - final ImageMounter.Mount fsmnt2 = mounter.mount(rawmnt2, workdir.resolve("upperDir.fs"), fsType); - final Path upperDir = fsmnt2.getMountPoint(); + final ImageMounter.Mount fsmnt = mounter.mount(rawmnt, workdir.resolve("backingFileId.fs"), fsType); + final Path lowerDir = fsmnt.getMountPoint(); - final Path outputDir = this.getWorkingDir().resolve("partition-" + partition.getIndex()); + final ImageMounter.Mount fsmnt2 = mounter.mount(rawmnt2, workdir.resolve("upperDir.fs"), fsType); + final Path upperDir = fsmnt2.getMountPoint(); - LOG.info("Executing RSYNC to determine changed files..."); - ProcessRunner processRunner = new ProcessRunner("rsync"); - processRunner.addArguments("-armxv"); //, "--progress"); when using --progress, rsync sometimes hangs... - processRunner.addArguments("--exclude", "dev"); - processRunner.addArguments("--exclude", "proc"); - processRunner.addArguments("--compare-dest=" + lowerDir.toAbsolutePath().toString() + "/", - upperDir.toAbsolutePath().toString() + "/", - outputDir.toAbsolutePath().toString()); - processRunner.execute(true); - processRunner.cleanup(); + final Path outputDir = this.getWorkingDir().resolve("partition-" + partition.getIndex()); - LOG.info("Done with rsync!"); + LOG.info("Executing RSYNC to determine changed files..."); + ProcessRunner processRunner = new ProcessRunner("rsync"); + processRunner.addArguments("-armxv"); //, "--progress"); when using --progress, rsync sometimes hangs... + processRunner.addArguments("--exclude", "dev"); + processRunner.addArguments("--exclude", "proc"); + processRunner.addArguments("--compare-dest=" + lowerDir.toAbsolutePath().toString() + "/", + upperDir.toAbsolutePath().toString() + "/", + outputDir.toAbsolutePath().toString()); + processRunner.execute(true); + processRunner.cleanup(); + LOG.info("Done with rsync!"); - partitionFiles.add(outputDir); - ProcessRunner cleaner = new ProcessRunner("find"); - cleaner.addArguments(outputDir.toAbsolutePath().toString(), "-empty", "-delete"); - cleaner.execute(true); - cleaner.cleanup(); - } + partitionFiles.add(outputDir); - Path outputTar = workdir.resolve("output.tgz"); - ProcessRunner tarCollectProc = new ProcessRunner("tar"); - tarCollectProc.addArguments("-czf"); - tarCollectProc.addArguments(outputTar.toAbsolutePath().toString()); - tarCollectProc.addArguments("-C", this.getWorkingDir().toAbsolutePath().toString()); - partitionFiles.forEach(p -> tarCollectProc.addArguments(p.getFileName().toString())); - tarCollectProc.execute(true); - tarCollectProc.cleanup(); + ProcessRunner cleaner = new ProcessRunner("find"); + cleaner.addArguments(outputDir.toAbsolutePath().toString(), "-empty", "-delete"); + cleaner.execute(true); + cleaner.cleanup(); + } - return outputTar; - } - catch (BWFLAException cause) { - cause.printStackTrace(); - throw cause; - } - catch (IOException ioException) - { + Path outputTar = workdir.resolve("output.tgz"); + ProcessRunner tarCollectProc = new ProcessRunner("tar"); + tarCollectProc.addArguments("-czf"); + tarCollectProc.addArguments(outputTar.toAbsolutePath().toString()); + tarCollectProc.addArguments("-C", this.getWorkingDir().toAbsolutePath().toString()); + partitionFiles.forEach(p -> tarCollectProc.addArguments(p.getFileName().toString())); + tarCollectProc.execute(true); + tarCollectProc.cleanup(); + + return outputTar; + } catch (BWFLAException cause) { + cause.printStackTrace(); + throw cause; + } catch (IOException ioException) { ioException.printStackTrace(); final String message = "IO Exception in getChangedFiles"; - throw new BWFLAException(message, ioException) - .setId(this.getComponentId()); + throw new BWFLAException(message, ioException) + .setId(this.getComponentId()); } - } - + } - private void processEmulatorOutput() throws BWFLAException - { - LOG.info("Processing emulator output ..."); - final String bindingId = emuEnvironment.getOutputBindingId(); - Path outputTar = null; + private void processEmulatorOutput() throws BWFLAException { + LOG.info("Processing emulator output ..."); - Binding b = bindings.get(bindingId); - if(b == null) { - final String message = "Unknown output bindingId " + bindingId; - final BWFLAException error = new BWFLAException(message) - .setId(this.getComponentId()); + final String bindingId = emuEnvironment.getOutputBindingId(); + Path outputTar = null; - this.result.completeExceptionally(error); - } + Binding b = bindings.get(bindingId); + if (b == null) { + final String message = "Unknown output bindingId " + bindingId; + final BWFLAException error = new BWFLAException(message) + .setId(this.getComponentId()); - try { + this.result.completeExceptionally(error); + } - if(emuEnvironment.isLinuxRuntime()){ + try { - for (AbstractDataResource r : emuEnvironment.getAbstractDataResource()) - { - if(r.getId() != null && r.getId().equals("rootfs")) - { - LOG.info("Linux runtime recognized, processing with rootfs..."); - final String cowImage = bindings.lookup(BindingsManager.toBindingId("rootfs", BindingsManager.EntryType.IMAGE)); - this.unmountBindings(); - outputTar = getChangedFiles(Path.of(cowImage)); + if (emuEnvironment.isLinuxRuntime()) { - } - } - } + for (AbstractDataResource r : emuEnvironment.getAbstractDataResource()) { + if (r.getId() != null && r.getId().equals("rootfs")) { + LOG.info("Linux runtime recognized, processing with rootfs..."); + final String cowImage = bindings.lookup(BindingsManager.toBindingId("rootfs", BindingsManager.EntryType.IMAGE)); + this.unmountBindings(); + outputTar = getChangedFiles(Path.of(cowImage)); - else { - final String qcow = bindings.lookup(BindingsManager.toBindingId(bindingId, BindingsManager.EntryType.IMAGE)); - this.unmountBindings(); - outputTar = getChangedFiles(Path.of(qcow)); - } + } + } + } else { + final String qcow = bindings.lookup(BindingsManager.toBindingId(bindingId, BindingsManager.EntryType.IMAGE)); + this.unmountBindings(); + outputTar = getChangedFiles(Path.of(qcow)); + } // final BlobDescription blob = new BlobDescription() // .setDescription("Output for session " + this.getComponentId()) @@ -1135,263 +1098,246 @@ private void processEmulatorOutput() throws BWFLAException // blob.setDataFromFile(outputTar); // blob.setType(".tgz"); - //TODO REPLACE WITH CLIENT CALL - // Upload archive to the BlobStore - BlobHandle handle = new BlobHandle(); + //TODO REPLACE WITH CLIENT CALL + // Upload archive to the BlobStore + BlobHandle handle = new BlobHandle(); if (handle == null) { throw new BWFLAException("Output result is null").setId(this.getComponentId()); - } - this.result.complete(handle); - } - catch (BWFLAException cause) { - final String message = "Creation of output.zip failed!"; - final BWFLAException error = new BWFLAException(message, cause) - .setId(this.getComponentId()); + } + this.result.complete(handle); + } catch (BWFLAException cause) { + final String message = "Creation of output.zip failed!"; + final BWFLAException error = new BWFLAException(message, cause) + .setId(this.getComponentId()); + + this.result.completeExceptionally(error); + throw error; + } + } + + private String getEmulatorOutputLocation() throws BWFLAException { + if (!this.isOutputAvailable()) + return null; + + try { + final BlobHandle handle = super.result.get(2, TimeUnit.MINUTES); + if (blobStoreRestAddress.contains("http://eaas:8080")) + return handle.toRestUrl(blobStoreRestAddress.replace("http://eaas:8080", "")); + else + return handle.toRestUrl(blobStoreRestAddress); + } catch (Exception error) { + final String message = "Waiting for emulator's output failed!"; + LOG.log(Level.WARNING, message, error); + throw new BWFLAException(message, error) + .setId(this.getComponentId()); + } + } - this.result.completeExceptionally(error); - throw error; - } - } - - private String getEmulatorOutputLocation() throws BWFLAException - { - if (!this.isOutputAvailable()) - return null; - - try { - final BlobHandle handle = super.result.get(2, TimeUnit.MINUTES); - if (blobStoreRestAddress.contains("http://eaas:8080")) - return handle.toRestUrl(blobStoreRestAddress.replace("http://eaas:8080", "")); - else - return handle.toRestUrl(blobStoreRestAddress); - } - catch (Exception error) { - final String message = "Waiting for emulator's output failed!"; - LOG.log(Level.WARNING, message, error); - throw new BWFLAException(message, error) - .setId(this.getComponentId()); - } - } - - void stopInternal() - { - if (player != null) - player.stop(); - - this.closeAllConnectors(); - - if (emuRunner.isProcessRunning()) - this.stopProcessRunner(emuRunner); - - if (printer != null) - printer.stop(); - - final var stdoutCon = (LogConnector) this.getControlConnector(StdoutLogConnector.PROTOCOL); - if(stdoutCon != null) - stdoutCon.cleanup(); - - final var stderrCon = (LogConnector) this.getControlConnector(StderrLogConnector.PROTOCOL); - if(stderrCon != null) - stderrCon.cleanup(); - - } - - private void closeAllConnectors() - { - if (this.isSdlBackendEnabled()) { - final GuacamoleConnector connector = (GuacamoleConnector) this.getControlConnector(GuacamoleConnector.PROTOCOL); - final GuacTunnel tunnel = (connector != null) ? connector.getTunnel() : null; - if (tunnel != null && tunnel.isOpen()) { - try { - tunnel.disconnect(); - tunnel.close(); - } - catch (GuacamoleException error) { - LOG.log(Level.SEVERE, "Closing Guacamole connector failed!", error); - } - } - } - else if (this.isXpraBackendEnabled()) { - final XpraConnector connector = (XpraConnector) this.getControlConnector(XpraConnector.PROTOCOL); - if (connector != null) { - try { - connector.disconnect(); - } - catch (Exception error) { - LOG.log(Level.SEVERE, "Closing Xpra connector failed!", error); - } - } - } + void stopInternal() { + if (player != null) + player.stop(); - if (this.isPulseAudioEnabled()) { - final AudioConnector connector = (AudioConnector) this.getControlConnector(AudioConnector.PROTOCOL); - final IAudioStreamer streamer = (connector != null) ? connector.getAudioStreamer() : null; - if (streamer != null && !streamer.isClosed()) { - try { - streamer.stop(); - streamer.close(); - } - catch (Exception error) { - LOG.log(Level.WARNING, "Stopping audio streamer failed!", error); - } - } - } - } - - void stopProcessRunner(ProcessRunner runner) - { - final int emuProcessId = runner.getProcessId(); - final var emuContainerId = this.getContainerId(); - if (this.isContainerModeEnabled()) - LOG.info("Stopping emulator " + emuProcessId + " in container " + emuContainerId + "..."); - else LOG.info("Stopping emulator " + emuProcessId + "..."); - - try { - if (this.isSdlBackendEnabled()) { - // Send termination message - ctlMsgWriter.begin(MessageType.TERMINATE); - ctlMsgWriter.send(emuCtlSocketName); - - // Give emulator a chance to shutdown cleanly - if (runner.waitUntilFinished(5, TimeUnit.SECONDS)) { - LOG.info("Emulator " + emuProcessId + " stopped."); - return; - } - } - } - catch (Exception exception) { - LOG.log(Level.SEVERE, "Stopping emulator failed!", exception); - } + this.closeAllConnectors(); - if (this.isContainerModeEnabled()) { - final var killer = new ProcessRunner(); - final var cmds = new ArrayList>(2); - cmds.add(List.of("runc", "kill", emuContainerId, "TERM")); - cmds.add(List.of("runc", "kill", "-a", emuContainerId, "KILL")); - for (final var args : cmds) { - killer.setCommand("sudo"); - killer.addArguments(args); - killer.setLogger(LOG); - killer.execute(); - - if (runner.waitUntilFinished(5, TimeUnit.SECONDS)) { - LOG.info("Emulator " + emuProcessId + " stopped."); - return; - } - } - } + if (emuRunner.isProcessRunning()) + this.stopProcessRunner(emuRunner); - LOG.warning("Emulator " + emuProcessId + " failed to shutdown cleanly! Killing it..."); - runner.stop(5, TimeUnit.SECONDS); // Try to terminate the process - runner.kill(); // Try to kill the process - } + if (printer != null) + printer.stop(); - @Override - public String getRuntimeConfiguration() throws BWFLAException - { - synchronized (emuBeanState) - { - if (emuBeanState.get() == EmuCompState.EMULATOR_UNDEFINED) { - String message = "Runtime configuration is not available in this state!"; - throw new IllegalEmulatorStateException(message, EmuCompState.EMULATOR_UNDEFINED) - .setId(this.getComponentId()); - } - } - try { - return this.emuEnvironment.value(); - } catch (JAXBException e) { - throw new BWFLAException("Serializing environment description failed!", e) - .setId(this.getComponentId()); - } - } - - @Override - public Set getColdplugableDrives() - { - // TODO: here read result from corresponding metadata - return new HashSet(); - } - - @Override - public Set getHotplugableDrives() - { - // TODO: here read result from corresponding metadata - return new HashSet(); - } - - private void sync() throws BWFLAException - { - final ProcessRunner process = new ProcessRunner(); - process.setCommand("sync"); - if (!process.execute()) { - throw new BWFLAException("Syncing filesystem failed!") - .setId(this.getComponentId()); - } + final var stdoutCon = (LogConnector) this.getControlConnector(StdoutLogConnector.PROTOCOL); + if (stdoutCon != null) + stdoutCon.cleanup(); - LOG.info("filesystem synced"); - } + final var stderrCon = (LogConnector) this.getControlConnector(StderrLogConnector.PROTOCOL); + if (stderrCon != null) + stderrCon.cleanup(); - @Override - public List snapshot() throws BWFLAException - { - synchronized (emuBeanState) { - final EmuCompState curstate = emuBeanState.get(); - if (curstate != EmuCompState.EMULATOR_STOPPED) { - String message = "Cannot save environment in this state!"; - throw new IllegalEmulatorStateException(message, curstate) - .setId(this.getComponentId()); - } - } + } - // Collect all modified images for used/mounted bindings - final Map images = new LinkedHashMap(); - for (AbstractDataResource resource : emuEnvironment.getAbstractDataResource()) { - if (!(resource instanceof Binding)) - continue; - - final Binding binding = (Binding) resource; - if(((Binding) resource).getAccess() == Binding.AccessType.COPY) - continue; - - final String id = binding.getId(); - - final String path = bindings.lookup(BindingsManager.toBindingId(id, BindingsManager.EntryType.IMAGE)); - if (path == null) { - LOG.info("Binding not used/mounted! Skipping: " + id); - continue; - } + private void closeAllConnectors() { + if (this.isSdlBackendEnabled()) { + final GuacamoleConnector connector = (GuacamoleConnector) this.getControlConnector(GuacamoleConnector.PROTOCOL); + final GuacTunnel tunnel = (connector != null) ? connector.getTunnel() : null; + if (tunnel != null && tunnel.isOpen()) { + try { + tunnel.disconnect(); + tunnel.close(); + } catch (GuacamoleException error) { + LOG.log(Level.SEVERE, "Closing Guacamole connector failed!", error); + } + } + } else if (this.isXpraBackendEnabled()) { + final XpraConnector connector = (XpraConnector) this.getControlConnector(XpraConnector.PROTOCOL); + if (connector != null) { + try { + connector.disconnect(); + } catch (Exception error) { + LOG.log(Level.SEVERE, "Closing Xpra connector failed!", error); + } + } + } - images.put(id, path); - } + if (this.isPulseAudioEnabled()) { + final AudioConnector connector = (AudioConnector) this.getControlConnector(AudioConnector.PROTOCOL); + final IAudioStreamer streamer = (connector != null) ? connector.getAudioStreamer() : null; + if (streamer != null && !streamer.isClosed()) { + try { + streamer.stop(); + streamer.close(); + } catch (Exception error) { + LOG.log(Level.WARNING, "Stopping audio streamer failed!", error); + } + } + } + } + + void stopProcessRunner(ProcessRunner runner) { + final int emuProcessId = runner.getProcessId(); + final var emuContainerId = this.getContainerId(); + if (this.isContainerModeEnabled()) + LOG.info("Stopping emulator " + emuProcessId + " in container " + emuContainerId + "..."); + else LOG.info("Stopping emulator " + emuProcessId + "..."); + + try { + if (this.isSdlBackendEnabled()) { + // Send termination message + ctlMsgWriter.begin(MessageType.TERMINATE); + ctlMsgWriter.send(emuCtlSocketName); + + // Give emulator a chance to shutdown cleanly + if (runner.waitUntilFinished(5, TimeUnit.SECONDS)) { + LOG.info("Emulator " + emuProcessId + " stopped."); + return; + } + } + } catch (Exception exception) { + LOG.log(Level.SEVERE, "Stopping emulator failed!", exception); + } + + if (this.isContainerModeEnabled()) { + final var killer = new ProcessRunner(); + final var cmds = new ArrayList>(2); + cmds.add(List.of("runc", "kill", emuContainerId, "TERM")); + cmds.add(List.of("runc", "kill", "-a", emuContainerId, "KILL")); + for (final var args : cmds) { + killer.setCommand("sudo"); + killer.addArguments(args); + killer.setLogger(LOG); + killer.execute(); + + if (runner.waitUntilFinished(5, TimeUnit.SECONDS)) { + LOG.info("Emulator " + emuProcessId + " stopped."); + return; + } + } + } + + LOG.warning("Emulator " + emuProcessId + " failed to shutdown cleanly! Killing it..."); + runner.stop(5, TimeUnit.SECONDS); // Try to terminate the process + runner.kill(); // Try to kill the process + } + + @Override + public String getRuntimeConfiguration() throws BWFLAException { + synchronized (emuBeanState) { + if (emuBeanState.get() == EmuCompState.EMULATOR_UNDEFINED) { + String message = "Runtime configuration is not available in this state!"; + throw new IllegalEmulatorStateException(message, EmuCompState.EMULATOR_UNDEFINED) + .setId(this.getComponentId()); + } + } + try { + return this.emuEnvironment.value(); + } catch (JAXBException e) { + throw new BWFLAException("Serializing environment description failed!", e) + .setId(this.getComponentId()); + } + } + + @Override + public Set getColdplugableDrives() { + // TODO: here read result from corresponding metadata + return new HashSet(); + } + + @Override + public Set getHotplugableDrives() { + // TODO: here read result from corresponding metadata + return new HashSet(); + } + + private void sync() throws BWFLAException { + final ProcessRunner process = new ProcessRunner(); + process.setCommand("sync"); + if (!process.execute()) { + throw new BWFLAException("Syncing filesystem failed!") + .setId(this.getComponentId()); + } + + LOG.info("filesystem synced"); + } + + @Override + public List snapshot() throws BWFLAException { + synchronized (emuBeanState) { + final EmuCompState curstate = emuBeanState.get(); + if (curstate != EmuCompState.EMULATOR_STOPPED) { + String message = "Cannot save environment in this state!"; + throw new IllegalEmulatorStateException(message, curstate) + .setId(this.getComponentId()); + } + } + + // Collect all modified images for used/mounted bindings + final Map images = new LinkedHashMap(); + for (AbstractDataResource resource : emuEnvironment.getAbstractDataResource()) { + if (!(resource instanceof Binding)) + continue; + + final Binding binding = (Binding) resource; + if (((Binding) resource).getAccess() == Binding.AccessType.COPY) + continue; - // Now it should be safe to unmount! - this.unmountBindings(); - this.sync(); + final String id = binding.getId(); - // TODO: filter out all unchanged images with qemu-img compare! - // replace with client + final String path = bindings.lookup(BindingsManager.toBindingId(id, BindingsManager.EntryType.IMAGE)); + if (path == null) { + LOG.info("Binding not used/mounted! Skipping: " + id); + continue; + } + + images.put(id, path); + } + + // Now it should be safe to unmount! + this.unmountBindings(); + this.sync(); + + // TODO: filter out all unchanged images with qemu-img compare! + // replace with client // final BlobStore blobstore = BlobStoreClient.get() // .getBlobStorePort(blobStoreAddressSoap); - // Create one DataHandler per image - final List handlers = new ArrayList(); + // Create one DataHandler per image + final List handlers = new ArrayList(); // try { - for (Map.Entry entry : images.entrySet()) { - final String id = entry.getKey(); - final String path = entry.getValue(); - - final BlobDescription blob = new BlobDescription() - .setDescription("Snapshot for session " + this.getComponentId()) - .setNamespace("emulator-snapshots") - .setDataFromFile(Paths.get(path)) - .setType(".qcow") - .setName(id); - - // Upload image to blobstore and register cleanup handler - //TODO REPLACE WITH CLIENT - final BlobHandle handle = new BlobHandle(); + for (Map.Entry entry : images.entrySet()) { + final String id = entry.getKey(); + final String path = entry.getValue(); + + final BlobDescription blob = new BlobDescription() + .setDescription("Snapshot for session " + this.getComponentId()) + .setNamespace("emulator-snapshots") + .setDataFromFile(Paths.get(path)) + .setType(".qcow") + .setName(id); + + // Upload image to blobstore and register cleanup handler + //TODO REPLACE WITH CLIENT + final BlobHandle handle = new BlobHandle(); // cleanups.push("delete-blob/" + handle.getId(), () -> { // try { // blobstore.delete(handle); @@ -1401,13 +1347,13 @@ public List snapshot() throws BWFLAException // } // }); - final String location = handle.toRestUrl(blobStoreRestAddress); - final BindingDataHandler handler = new BindingDataHandler() - .setUrl(location) - .setId(id); + final String location = handle.toRestUrl(blobStoreRestAddress); + final BindingDataHandler handler = new BindingDataHandler() + .setUrl(location) + .setId(id); - handlers.add(handler); - } + handlers.add(handler); + } // } // catch (BWFLAException error) { // LOG.log(Level.WARNING, "Uploading images failed!", error); @@ -1415,41 +1361,39 @@ public List snapshot() throws BWFLAException // throw error; // } - return handlers; - } + return handlers; + } - @Override - public int changeMedium(int containerId, String objReference) throws BWFLAException - { - try { - LOG.info("change medium: " + objReference); - Drive drive = this.emuEnvironment.getDrive().get(containerId); - // detach the current medium - this.connectDrive(drive, false); + @Override + public int changeMedium(int containerId, String objReference) throws BWFLAException { + try { + LOG.info("change medium: " + objReference); + Drive drive = this.emuEnvironment.getDrive().get(containerId); + // detach the current medium + this.connectDrive(drive, false); - if (objReference == null || objReference.isEmpty()) { - return containerId; - } + if (objReference == null || objReference.isEmpty()) { + return containerId; + } - drive.setData(objReference); - boolean attachOk = (emuBeanState.fetch() == EmuCompState.EMULATOR_RUNNING) ? connectDrive(drive, true) : addDrive(drive); + drive.setData(objReference); + boolean attachOk = (emuBeanState.fetch() == EmuCompState.EMULATOR_RUNNING) ? connectDrive(drive, true) : addDrive(drive); - if (!attachOk) { - throw new BWFLAException("error occured in the last phase of device attachment") - .setId(this.getComponentId()); - } - } catch (IndexOutOfBoundsException e) { - throw new BWFLAException("Cannot change medium: invalid drive id given.", e) - .setId(this.getComponentId()); - } - // TODO: change disk in run-time - return containerId; - } - - @Override - @Deprecated - public int attachMedium(DataHandler data, String mediumType) throws BWFLAException - { + if (!attachOk) { + throw new BWFLAException("error occured in the last phase of device attachment") + .setId(this.getComponentId()); + } + } catch (IndexOutOfBoundsException e) { + throw new BWFLAException("Cannot change medium: invalid drive id given.", e) + .setId(this.getComponentId()); + } + // TODO: change disk in run-time + return containerId; + } + + @Override + @Deprecated + public int attachMedium(DataHandler data, String mediumType) throws BWFLAException { /* synchronized (emuBeanState) { @@ -1519,12 +1463,11 @@ public int attachMedium(DataHandler data, String mediumType) throws BWFLAExcepti return id; } */ - return -1; - } + return -1; + } - @Override - public DataHandler detachMedium(int containerId) throws BWFLAException - { + @Override + public DataHandler detachMedium(int containerId) throws BWFLAException { /* synchronized (emuBeanState) { @@ -1572,1115 +1515,1043 @@ public DataHandler detachMedium(int containerId) throws BWFLAException .setId(this.getComponentId()); */ - return null; - } - - - // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Protected - // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - private EmulatorBeanMode getEmuBeanMode(MachineConfiguration config) throws IllegalArgumentException - { - final UiOptions options = config.getUiOptions(); - if (options != null && options.getForwarding_system() != null) { - var ret = EmulatorBeanMode.valueOf(options.getForwarding_system()); - if(ret == EmulatorBeanMode.HEADLESS && !this.isHeadlessSupported()) - return EmulatorBeanMode.SDLONP; - return ret; - } - else return EmulatorBeanMode.SDLONP; - } - - protected void prepareUserData(MachineConfiguration environment) throws BWFLAException { - File src = new File(emulatorDataBase + getEmuContainerName(environment)); - if(!src.exists()) { - LOG.info("no user data folder found " + src.getAbsolutePath()); - return; - } - - File userData = new File(getDataDir().toFile(), getEmuContainerName(environment)); - if(userData.exists()) - throw new BWFLAException("copied user data twice"); - - ProcessRunner cp = new ProcessRunner(); - cp.setCommand("cp"); - cp.addArgument("-rv"); - cp.addArgument(src.getAbsolutePath()); - cp.addArgument(userData.getAbsolutePath()); - if(!cp.execute()) - throw new BWFLAException("preparing emulator user data failed."); - } - - protected void setRuntimeConfiguration(MachineConfiguration environment) throws BWFLAException - { - try { - this.emuEnvironment = environment; - LOG.info(emuEnvironment.value()); - for (AbstractDataResource resource: emuEnvironment.getAbstractDataResource()) - this.prepareResource(resource); - - prepareUserData(environment); - - MachineConfiguration.NativeConfig nativeConfig = emuEnvironment.getNativeConfig(); - this.prepareNativeConfig(nativeConfig); - - UiOptions uiOptions = emuEnvironment.getUiOptions(); - if (uiOptions != null) { - this.isPulseAudioEnabled = false; - if(uiOptions.getAudio_system() != null - && !uiOptions.getAudio_system().isEmpty() - && uiOptions.getAudio_system().equalsIgnoreCase("webrtc")) { - this.isPulseAudioEnabled = true; - } - } - - this.prepareEmulatorRunner(); - - if (uiOptions != null) { - TimeOptions timeOptions = uiOptions.getTime(); - if (timeOptions != null) { - if (timeOptions.getEpoch() != null) { - long epoch = Long.parseLong(timeOptions.getEpoch()); - this.setEmulatorTime(epoch); - } else if (timeOptions.getOffset() != null) { - long offset = Long.parseLong(timeOptions.getEpoch()); - this.setEmulatorTime(offset); - } - } - } - - this.setupEmulatorBackend(); - - for(Drive drive: emuEnvironment.getDrive()) - prepareDrive(drive); - - for(Nic nic: emuEnvironment.getNic()) - prepareNic(nic); - - this.finishRuntimeConfiguration(); - - } catch (IllegalArgumentException | IOException | JAXBException e) { - throw new BWFLAException("Could not set runtime information.", e) - .setId(this.getComponentId()); - } - } - - protected String getNativeConfig() - { - return emuNativeConfig; - } - - /** Must be overriden by subclasses to initialize the emulator's command. */ - protected abstract void prepareEmulatorRunner() throws BWFLAException; - - /** Callback for performing actions, deferred during runtime configuration. */ - protected void finishRuntimeConfiguration() throws BWFLAException - { - // Do nothing! - } - - /** - * Determine the image format for a specified drive type for the current - * emulator. - *

- * This method should be overridden by any emulator that has specific - * file format needs (e.g. VirtualBox (yuck)). - * - * @param driveType The drive type - * @return The desired image format for the specified drive type - */ - protected XmountOutputFormat getImageFormatForDriveType(Drive.DriveType driveType) { - // as default, we use raw images for everything - return XmountOutputFormat.RAW; - } - - /** Setups the emulator's backend */ - private void setupEmulatorBackend() - { - switch (emuBeanMode) - { - case SDLONP: - this.setupEmulatorForSDLONP(); - break; - - case Y11: - this.setupEmulatorForY11(); - break; - - case XPRA: - // Nothing to setup! - } - } - - /** Setups the emulator's environment variables for running locally. */ - private void setupEmulatorForY11() - { - protocol = PROTOCOL_Y11; - - final String emusocket = this.getSocketsDir() - .resolve("sdlonp-iosocket-emu").toString(); - - // Setup emulator's tunnel - final GuacamoleConfiguration gconf = tunnelConfig.getGuacamoleConfiguration(); - gconf.setProtocol(PROTOCOL_SDLONP); - gconf.setParameter("enable-audio", "false"); - gconf.setParameter("emu-iosocket", emusocket); - - // Setup emulator's environment - emuRunner.addEnvVariable("SDL_VIDEODRIVER", protocol); - if (this.isPulseAudioEnabled()) - emuRunner.addEnvVariable("SDL_AUDIODRIVER", AUDIODRIVER_PULSE); - - emuRunner.addEnvVariable("SDL_SRVCTLSOCKET", ctlSocket.getName()); - emuRunner.addEnvVariable("SDL_EMUCTLSOCKET", emuCtlSocketName); - emuRunner.addEnvVariable("ALSA_CARD", alsa_card); - emuConfig.setIoSocket(emusocket); - - // TODO: should this parameter be read from meta-data? - emuConfig.setInactivityTimeout(this.getInactivityTimeout()); - - UiOptions uiopts = emuEnvironment.getUiOptions(); - if (uiopts == null) - return; - - Html5Options html5 = uiopts.getHtml5(); - if (html5 == null) - return; - - if (html5.isPointerLock()) - emuConfig.setRelativeMouse(true); - } - - private String fmtDate(long epoch) - { - Date d = new Date(epoch); - DateFormat format = new SimpleDateFormat("YYYY-MM-dd hh:mm:ss"); - String formatted = format.format(d); - return formatted; - } - - protected void setEmulatorTime(long epoch) - { - //LOG.info("set emulator time: " + epoch + " fmtStr " + fmtDate(epoch)); - // emuRunner.addEnvVariable("FAKETIME", ""+ fmtDate(epoch)); - // emuRunner.addEnvVariable("LD_PRELOAD", libfaketime); - } - - protected void setEmulatorTimeOffset(long offset) - { - // emuRunner.addEnvVariable("FAKE_TIME_OFFSET", ""+offset); - // emuRunner.addEnvVariable("LD_PRELOAD", "/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1"); - } - - /** Setups the emulator's environment variables and tunnel for SDLONP-Protocol. */ - private void setupEmulatorForSDLONP() - { - protocol = PROTOCOL_SDLONP; - - final String emusocket = this.getSocketsDir() - .resolve("sdlonp-iosocket-emu").toString(); - - // Setup emulator's tunnel - final GuacamoleConfiguration gconf = tunnelConfig.getGuacamoleConfiguration(); - gconf.setProtocol(protocol); - gconf.setParameter("enable-audio", Boolean.toString(!this.isPulseAudioEnabled())); - gconf.setParameter("emu-iosocket", emusocket); - - // Setup client configuration - if (!this.isPulseAudioEnabled()) { - final GuacamoleClientInformation ginfo = tunnelConfig.getGuacamoleClientInformation(); - ginfo.getAudioMimetypes().add("audio/ogg"); - } - - // Setup emulator's environment - emuRunner.addEnvVariable("SDL_AUDIODRIVER", (this.isPulseAudioEnabled()) ? AUDIODRIVER_PULSE : protocol); - emuRunner.addEnvVariable("SDL_VIDEODRIVER", protocol); - emuRunner.addEnvVariable("SDL_SRVCTLSOCKET", ctlSocket.getName()); - emuRunner.addEnvVariable("SDL_EMUCTLSOCKET", emuCtlSocketName); - emuConfig.setIoSocket(emusocket); - - // HACK: Qemu uses a custom audio setup! - if (this instanceof QemuBean && this.isPulseAudioEnabled() ) { - emuRunner.getEnvVariables() - .remove("SDL_AUDIODRIVER"); - } - - emuConfig.setInactivityTimeout(this.getInactivityTimeout()); - - UiOptions uiopts = emuEnvironment.getUiOptions(); - if (uiopts != null) { - Html5Options html5 = uiopts.getHtml5(); - if (html5 != null) { - if (html5.isPointerLock()) - emuConfig.setRelativeMouse(true); - - String crtopt = html5.getCrt(); - if (crtopt != null && !crtopt.isEmpty()) { - emuConfig.setCrtFilter("snes-ntsc"); - emuConfig.setCrtPreset("composite"); - } - } - - InputOptions input = uiopts.getInput(); - if (input != null) { - String kbdModel = input.getEmulatorKbdModel(); - if (kbdModel != null && !kbdModel.isEmpty()) - emuConfig.setKeyboardModel(kbdModel); - - String kbdLayout = input.getEmulatorKbdLayout(); - if (kbdLayout != null && !kbdLayout.isEmpty()) - emuConfig.setKeyboardLayout(kbdLayout); + return null; + } + + + // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Protected + // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private EmulatorBeanMode getEmuBeanMode(MachineConfiguration config) throws IllegalArgumentException { + final UiOptions options = config.getUiOptions(); + if (options != null && options.getForwarding_system() != null) { + var ret = EmulatorBeanMode.valueOf(options.getForwarding_system()); + if (ret == EmulatorBeanMode.HEADLESS && !this.isHeadlessSupported()) + return EmulatorBeanMode.SDLONP; + return ret; + } else return EmulatorBeanMode.SDLONP; + } + + protected void prepareUserData(MachineConfiguration environment) throws BWFLAException { + File src = new File(emulatorDataBase + getEmuContainerName(environment)); + if (!src.exists()) { + LOG.info("no user data folder found " + src.getAbsolutePath()); + return; + } - String clientKbdModel = input.getClientKbdModel(); - if (clientKbdModel != null && !clientKbdModel.isEmpty()) - emuConfig.setClientKeyboardModel(clientKbdModel); + File userData = new File(getDataDir().toFile(), getEmuContainerName(environment)); + if (userData.exists()) + throw new BWFLAException("copied user data twice"); + + ProcessRunner cp = new ProcessRunner(); + cp.setCommand("cp"); + cp.addArgument("-rv"); + cp.addArgument(src.getAbsolutePath()); + cp.addArgument(userData.getAbsolutePath()); + if (!cp.execute()) + throw new BWFLAException("preparing emulator user data failed."); + } + + protected void setRuntimeConfiguration(MachineConfiguration environment) throws BWFLAException { + try { + this.emuEnvironment = environment; + LOG.info(emuEnvironment.value()); + for (AbstractDataResource resource : emuEnvironment.getAbstractDataResource()) + this.prepareResource(resource); + + prepareUserData(environment); + + MachineConfiguration.NativeConfig nativeConfig = emuEnvironment.getNativeConfig(); + this.prepareNativeConfig(nativeConfig); + + UiOptions uiOptions = emuEnvironment.getUiOptions(); + if (uiOptions != null) { + this.isPulseAudioEnabled = false; + if (uiOptions.getAudio_system() != null + && !uiOptions.getAudio_system().isEmpty() + && uiOptions.getAudio_system().equalsIgnoreCase("webrtc")) { + this.isPulseAudioEnabled = true; + } + } - String clientKbdLayout = input.getClientKbdLayout(); - if (clientKbdLayout != null && !clientKbdLayout.isEmpty()) - emuConfig.setClientKeyboardLayout(clientKbdLayout); - } - } - } + this.prepareEmulatorRunner(); + + if (uiOptions != null) { + TimeOptions timeOptions = uiOptions.getTime(); + if (timeOptions != null) { + if (timeOptions.getEpoch() != null) { + long epoch = Long.parseLong(timeOptions.getEpoch()); + this.setEmulatorTime(epoch); + } else if (timeOptions.getOffset() != null) { + long offset = Long.parseLong(timeOptions.getEpoch()); + this.setEmulatorTime(offset); + } + } + } + this.setupEmulatorBackend(); - /* ==================== EmuCon API ==================== */ + for (Drive drive : emuEnvironment.getDrive()) + prepareDrive(drive); - @Override - public DataHandler checkpoint() throws BWFLAException - { - if (!this.isContainerModeEnabled()) { - throw new BWFLAException("Container mode disabled! Checkpointing not possible.") - .setId(this.getComponentId()); - } + for (Nic nic : emuEnvironment.getNic()) + prepareNic(nic); - this.closeAllConnectors(); - if (this.isSdlBackendEnabled()) { - LOG.info("Waiting for emulator's detach-notification..."); - try { - this.waitForClientDetachAck(10, TimeUnit.SECONDS); - } catch (Exception e) { - throw new BWFLAException("Waiting for emulator's detach-notification failed!", e) - .setId(this.getComponentId()); - } - } + this.finishRuntimeConfiguration(); - final Path imgdir = this.getStateDir(); - try { - Files.createDirectories(imgdir); - } - catch (Exception error) { - throw new BWFLAException("Creating checkpoint directory failed!", error) - .setId(this.getComponentId()); - } - - final Path workdir = this.getWorkingDir(); - final Function relativizer = (abspath) -> { - final Path relpath = workdir.relativize(abspath); - return relpath.toString(); - }; + } catch (IllegalArgumentException | IOException | JAXBException e) { + throw new BWFLAException("Could not set runtime information.", e) + .setId(this.getComponentId()); + } + } + + protected String getNativeConfig() { + return emuNativeConfig; + } + + /** + * Must be overriden by subclasses to initialize the emulator's command. + */ + protected abstract void prepareEmulatorRunner() throws BWFLAException; + + /** + * Callback for performing actions, deferred during runtime configuration. + */ + protected void finishRuntimeConfiguration() throws BWFLAException { + // Do nothing! + } + + /** + * Determine the image format for a specified drive type for the current + * emulator. + *

+ * This method should be overridden by any emulator that has specific + * file format needs (e.g. VirtualBox (yuck)). + * + * @param driveType The drive type + * @return The desired image format for the specified drive type + */ + protected XmountOutputFormat getImageFormatForDriveType(Drive.DriveType driveType) { + // as default, we use raw images for everything + return XmountOutputFormat.RAW; + } + + /** + * Setups the emulator's backend + */ + private void setupEmulatorBackend() { + switch (emuBeanMode) { + case SDLONP: + this.setupEmulatorForSDLONP(); + break; + + case Y11: + this.setupEmulatorForY11(); + break; + + case XPRA: + // Nothing to setup! + } + } + + /** + * Setups the emulator's environment variables for running locally. + */ + private void setupEmulatorForY11() { + protocol = PROTOCOL_Y11; + + final String emusocket = this.getSocketsDir() + .resolve("sdlonp-iosocket-emu").toString(); + + // Setup emulator's tunnel + final GuacamoleConfiguration gconf = tunnelConfig.getGuacamoleConfiguration(); + gconf.setProtocol(PROTOCOL_SDLONP); + gconf.setParameter("enable-audio", "false"); + gconf.setParameter("emu-iosocket", emusocket); + + // Setup emulator's environment + emuRunner.addEnvVariable("SDL_VIDEODRIVER", protocol); + if (this.isPulseAudioEnabled()) + emuRunner.addEnvVariable("SDL_AUDIODRIVER", AUDIODRIVER_PULSE); + + emuRunner.addEnvVariable("SDL_SRVCTLSOCKET", ctlSocket.getName()); + emuRunner.addEnvVariable("SDL_EMUCTLSOCKET", emuCtlSocketName); + emuRunner.addEnvVariable("ALSA_CARD", alsa_card); + emuConfig.setIoSocket(emusocket); + + // TODO: should this parameter be read from meta-data? + emuConfig.setInactivityTimeout(this.getInactivityTimeout()); + + UiOptions uiopts = emuEnvironment.getUiOptions(); + if (uiopts == null) + return; + + Html5Options html5 = uiopts.getHtml5(); + if (html5 == null) + return; + + if (html5.isPointerLock()) + emuConfig.setRelativeMouse(true); + } + + private String fmtDate(long epoch) { + Date d = new Date(epoch); + DateFormat format = new SimpleDateFormat("YYYY-MM-dd hh:mm:ss"); + String formatted = format.format(d); + return formatted; + } + + protected void setEmulatorTime(long epoch) { + //LOG.info("set emulator time: " + epoch + " fmtStr " + fmtDate(epoch)); + // emuRunner.addEnvVariable("FAKETIME", ""+ fmtDate(epoch)); + // emuRunner.addEnvVariable("LD_PRELOAD", libfaketime); + } + + protected void setEmulatorTimeOffset(long offset) { + // emuRunner.addEnvVariable("FAKE_TIME_OFFSET", ""+offset); + // emuRunner.addEnvVariable("LD_PRELOAD", "/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1"); + } + + /** + * Setups the emulator's environment variables and tunnel for SDLONP-Protocol. + */ + private void setupEmulatorForSDLONP() { + protocol = PROTOCOL_SDLONP; + + final String emusocket = this.getSocketsDir() + .resolve("sdlonp-iosocket-emu").toString(); + + // Setup emulator's tunnel + final GuacamoleConfiguration gconf = tunnelConfig.getGuacamoleConfiguration(); + gconf.setProtocol(protocol); + gconf.setParameter("enable-audio", Boolean.toString(!this.isPulseAudioEnabled())); + gconf.setParameter("emu-iosocket", emusocket); + + // Setup client configuration + if (!this.isPulseAudioEnabled()) { + final GuacamoleClientInformation ginfo = tunnelConfig.getGuacamoleClientInformation(); + ginfo.getAudioMimetypes().add("audio/ogg"); + } - final Path checkpoint = workdir.resolve("checkpoint" + CHECKPOINT_FILE_EXTENSION); - final ProcessRunner process = new ProcessRunner(); + // Setup emulator's environment + emuRunner.addEnvVariable("SDL_AUDIODRIVER", (this.isPulseAudioEnabled()) ? AUDIODRIVER_PULSE : protocol); + emuRunner.addEnvVariable("SDL_VIDEODRIVER", protocol); + emuRunner.addEnvVariable("SDL_SRVCTLSOCKET", ctlSocket.getName()); + emuRunner.addEnvVariable("SDL_EMUCTLSOCKET", emuCtlSocketName); + emuConfig.setIoSocket(emusocket); + + // HACK: Qemu uses a custom audio setup! + if (this instanceof QemuBean && this.isPulseAudioEnabled()) { + emuRunner.getEnvVariables() + .remove("SDL_AUDIODRIVER"); + } - LOG.info("Checkpointing emulator-container " + this.getContainerId() + "..."); + emuConfig.setInactivityTimeout(this.getInactivityTimeout()); - // Try to checkpoint the container... - process.setCommand("emucon-checkpoint"); - process.addArgument("--non-interactive"); + UiOptions uiopts = emuEnvironment.getUiOptions(); + if (uiopts != null) { + Html5Options html5 = uiopts.getHtml5(); + if (html5 != null) { + if (html5.isPointerLock()) + emuConfig.setRelativeMouse(true); - for (String file : emuContainerFilesToCheckpoint) { - final Path path = Paths.get(file); - process.addArguments("--include", relativizer.apply(path)); - } + String crtopt = html5.getCrt(); + if (crtopt != null && !crtopt.isEmpty()) { + emuConfig.setCrtFilter("snes-ntsc"); + emuConfig.setCrtPreset("composite"); + } + } - process.addArguments("--image-dir", relativizer.apply(imgdir)); - process.addArguments("--output", checkpoint.toString()); - process.addArgument(this.getContainerId()); - process.setWorkingDirectory(workdir); - process.setLogger(LOG); - if (!process.execute()) { - throw new BWFLAException("Checkpointing emulator-container failed!") - .setId(this.getComponentId()); - } + InputOptions input = uiopts.getInput(); + if (input != null) { + String kbdModel = input.getEmulatorKbdModel(); + if (kbdModel != null && !kbdModel.isEmpty()) + emuConfig.setKeyboardModel(kbdModel); - return new DataHandler(new FileDataSource(checkpoint.toFile())); - } + String kbdLayout = input.getEmulatorKbdLayout(); + if (kbdLayout != null && !kbdLayout.isEmpty()) + emuConfig.setKeyboardLayout(kbdLayout); + String clientKbdModel = input.getClientKbdModel(); + if (clientKbdModel != null && !clientKbdModel.isEmpty()) + emuConfig.setClientKeyboardModel(clientKbdModel); - /* ==================== Session Recording Helpers ==================== */ + String clientKbdLayout = input.getClientKbdLayout(); + if (clientKbdLayout != null && !clientKbdLayout.isEmpty()) + emuConfig.setClientKeyboardLayout(clientKbdLayout); + } + } + } - public boolean prepareSessionRecorder() throws BWFLAException - { - if (recorder != null) { - LOG.info("SessionRecorder already prepared."); - return true; - } - if (player != null) { - String message = "Initialization of SessionRecorder failed, " - + "because SessionReplayer is already running. " - + "Using both at the same time is not supported!"; + /* ==================== EmuCon API ==================== */ - throw new BWFLAException(message) - .setId(this.getComponentId()); - } + @Override + public DataHandler checkpoint() throws BWFLAException { + if (!this.isContainerModeEnabled()) { + throw new BWFLAException("Container mode disabled! Checkpointing not possible.") + .setId(this.getComponentId()); + } - // Create and initialize the recorder - recorder = new SessionRecorder(this.getComponentId(), MESSAGE_BUFFER_CAPACITY); - try { - // Create and setup a temp-file for the recording - Path tmpfile = this.getDataDir().resolve(TRACE_FILE); - recorder.prepare(tmpfile); - } - catch (IOException exception) { - LOG.severe("Creation of output file for session-recording failed!"); - LOG.log(Level.SEVERE, exception.getMessage(), exception); - recorder = null; - return false; - } + this.closeAllConnectors(); + if (this.isSdlBackendEnabled()) { + LOG.info("Waiting for emulator's detach-notification..."); + try { + this.waitForClientDetachAck(10, TimeUnit.SECONDS); + } catch (Exception e) { + throw new BWFLAException("Waiting for emulator's detach-notification failed!", e) + .setId(this.getComponentId()); + } + } - // Register the recorder as interceptor - interceptors.addInterceptor(recorder); - - return true; - } - - public void startSessionRecording() throws BWFLAException - { - this.ensureRecorderIsInitialized(); - recorder.start(); - } - - public void stopSessionRecording() throws BWFLAException - { - this.ensureRecorderIsInitialized(); - recorder.stop(); - } - - public boolean isRecordModeEnabled() throws BWFLAException - { - if (recorder == null) - return false; - - return recorder.isRecording(); - } - - public void addActionFinishedMark() - { - // this.ensureRecorderIsInitialized(); - - InstructionBuilder ibuilder = new InstructionBuilder(16); - ibuilder.start(ExtOpCode.ACTION_FINISHED); - ibuilder.finish(); - - recorder.postMessage(SourceType.INTERNAL, ibuilder.array(), 0, ibuilder.length()); - } - - /** Add a new metadata chunk to the trace-file. */ - public void defineTraceMetadataChunk(String tag, String comment) throws BWFLAException - { - this.ensureRecorderIsInitialized(); - recorder.defineMetadataChunk(tag, comment); - } - - /** Add a key/value pair as metadata to the trace-file. */ - public void addTraceMetadataEntry(String ctag, String key, String value) throws BWFLAException - { - this.ensureRecorderIsInitialized(); - recorder.addMetadataEntry(ctag, key, value); - } - - public String getSessionTrace() throws BWFLAException - { - this.ensureRecorderIsInitialized(); - - try { - recorder.finish(); - } - catch (IOException exception) { - LOG.severe("Finishing session-recording failed!"); - LOG.log(Level.SEVERE, exception.getMessage(), exception); - return null; - } + final Path imgdir = this.getStateDir(); + try { + Files.createDirectories(imgdir); + } catch (Exception error) { + throw new BWFLAException("Creating checkpoint directory failed!", error) + .setId(this.getComponentId()); + } - return recorder.toString(); - } + final Path workdir = this.getWorkingDir(); + final Function relativizer = (abspath) -> { + final Path relpath = workdir.relativize(abspath); + return relpath.toString(); + }; - private void ensureRecorderIsInitialized() throws BWFLAException - { - if (recorder == null) { - throw new BWFLAException("SessionRecorder is not initialized!") - .setId(this.getComponentId()); - } - } + final Path checkpoint = workdir.resolve("checkpoint" + CHECKPOINT_FILE_EXTENSION); + final ProcessRunner process = new ProcessRunner(); + LOG.info("Checkpointing emulator-container " + this.getContainerId() + "..."); - /* ==================== Session Replay Helpers ==================== */ + // Try to checkpoint the container... + process.setCommand("emucon-checkpoint"); + process.addArgument("--non-interactive"); - public boolean prepareSessionPlayer(String trace, boolean headless) throws BWFLAException - { - if (player != null) { - LOG.info("SessionPlayer already prepared."); - return true; - } + for (String file : emuContainerFilesToCheckpoint) { + final Path path = Paths.get(file); + process.addArguments("--include", relativizer.apply(path)); + } - if (recorder != null) { - String message = "Initialization of SessionPlayer failed, " - + "because SessionRecorder is already running. " - + "Using both at the same time is not supported!"; + process.addArguments("--image-dir", relativizer.apply(imgdir)); + process.addArguments("--output", checkpoint.toString()); + process.addArgument(this.getContainerId()); + process.setWorkingDirectory(workdir); + process.setLogger(LOG); + if (!process.execute()) { + throw new BWFLAException("Checkpointing emulator-container failed!") + .setId(this.getComponentId()); + } - throw new BWFLAException(message) - .setId(this.getComponentId()); - } + return new DataHandler(new FileDataSource(checkpoint.toFile())); + } - Path file = this.getDataDir().resolve(TRACE_FILE); - try { - FileUtils.writeStringToFile(file.toFile(), trace); - } - catch (IOException exception) { - LOG.severe("An error occured while writing temporary session-trace!"); - LOG.log(Level.SEVERE, exception.getMessage(), exception); - return false; - } - player = new SessionPlayerWrapper(file, headless); + /* ==================== Session Recording Helpers ==================== */ - return true; - } + public boolean prepareSessionRecorder() throws BWFLAException { + if (recorder != null) { + LOG.info("SessionRecorder already prepared."); + return true; + } - public int getSessionPlayerProgress() - { - if (player == null) - return 0; + if (player != null) { + String message = "Initialization of SessionRecorder failed, " + + "because SessionReplayer is already running. " + + "Using both at the same time is not supported!"; - return player.getProgress(); - } + throw new BWFLAException(message) + .setId(this.getComponentId()); + } - public boolean isReplayModeEnabled() - { - if (player == null) - return false; + // Create and initialize the recorder + recorder = new SessionRecorder(this.getComponentId(), MESSAGE_BUFFER_CAPACITY); + try { + // Create and setup a temp-file for the recording + Path tmpfile = this.getDataDir().resolve(TRACE_FILE); + recorder.prepare(tmpfile); + } catch (IOException exception) { + LOG.severe("Creation of output file for session-recording failed!"); + LOG.log(Level.SEVERE, exception.getMessage(), exception); + recorder = null; + return false; + } - return player.isPlaying(); - } + // Register the recorder as interceptor + interceptors.addInterceptor(recorder); + + return true; + } + + public void startSessionRecording() throws BWFLAException { + this.ensureRecorderIsInitialized(); + recorder.start(); + } + + public void stopSessionRecording() throws BWFLAException { + this.ensureRecorderIsInitialized(); + recorder.stop(); + } + + public boolean isRecordModeEnabled() throws BWFLAException { + if (recorder == null) + return false; + + return recorder.isRecording(); + } + + public void addActionFinishedMark() { + // this.ensureRecorderIsInitialized(); + + InstructionBuilder ibuilder = new InstructionBuilder(16); + ibuilder.start(ExtOpCode.ACTION_FINISHED); + ibuilder.finish(); + + recorder.postMessage(SourceType.INTERNAL, ibuilder.array(), 0, ibuilder.length()); + } + + /** + * Add a new metadata chunk to the trace-file. + */ + public void defineTraceMetadataChunk(String tag, String comment) throws BWFLAException { + this.ensureRecorderIsInitialized(); + recorder.defineMetadataChunk(tag, comment); + } + + /** + * Add a key/value pair as metadata to the trace-file. + */ + public void addTraceMetadataEntry(String ctag, String key, String value) throws BWFLAException { + this.ensureRecorderIsInitialized(); + recorder.addMetadataEntry(ctag, key, value); + } + + public String getSessionTrace() throws BWFLAException { + this.ensureRecorderIsInitialized(); + + try { + recorder.finish(); + } catch (IOException exception) { + LOG.severe("Finishing session-recording failed!"); + LOG.log(Level.SEVERE, exception.getMessage(), exception); + return null; + } - /* ==================== Monitoring API ==================== */ + return recorder.toString(); + } - @Override - public boolean updateMonitorValues() - { - ProcessMonitor monitor = emuRunner.getProcessMonitor(); - if (monitor == null) { - // Process is currently not running! - return false; - } + private void ensureRecorderIsInitialized() throws BWFLAException { + if (recorder == null) { + throw new BWFLAException("SessionRecorder is not initialized!") + .setId(this.getComponentId()); + } + } - return monitor.update(); - } - @Override - public String getMonitorValue(ProcessMonitorVID id) - { - ProcessMonitor monitor = emuRunner.getProcessMonitor(); - if (monitor == null) { - // Process is currently not running! - return ProcessMonitor.INVALID_VALUE; - } + /* ==================== Session Replay Helpers ==================== */ - return monitor.getValue(id); - } + public boolean prepareSessionPlayer(String trace, boolean headless) throws BWFLAException { + if (player != null) { + LOG.info("SessionPlayer already prepared."); + return true; + } - @Override - public List getMonitorValues(Collection ids) - { - ProcessMonitor monitor = emuRunner.getProcessMonitor(); - if (monitor == null) { - // Process is currently not running! - return ProcessMonitor.INVALID_VALUE_LIST; - } + if (recorder != null) { + String message = "Initialization of SessionPlayer failed, " + + "because SessionRecorder is already running. " + + "Using both at the same time is not supported!"; - return monitor.getValues(ids); - } + throw new BWFLAException(message) + .setId(this.getComponentId()); + } - @Override - public List getAllMonitorValues() - { - ProcessMonitor monitor = emuRunner.getProcessMonitor(); - if (monitor == null) { - // Process is currently not running! - return ProcessMonitor.INVALID_VALUE_LIST; - } + Path file = this.getDataDir().resolve(TRACE_FILE); + try { + FileUtils.writeStringToFile(file.toFile(), trace); + } catch (IOException exception) { + LOG.severe("An error occured while writing temporary session-trace!"); + LOG.log(Level.SEVERE, exception.getMessage(), exception); + return false; + } - return monitor.getValues(); - } - - - /* ==================== PostScriptPrinter API ==================== */ - - public List getPrintJobs() throws BWFLAException{ - if(printer == null) - return null; - - return printer.getPrintJobs(); - } - - /* ==================== Screenshot API ==================== */ - - public void takeScreenshot() - { - if(scrshooter != null) - scrshooter.takeScreenshot(); - } - - public DataHandler getNextScreenshot() - { - if(scrshooter == null) - return null; - - byte[] data = scrshooter.getNextScreenshot(); - if (data == null) - return null; - - return new DataHandler(data, "application/octet-stream"); - } - - - // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Utilities - // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // - // private int allocateContainerId(Container container) - // { - // int freeId = -1; - // final int MAX_TRIES = 50; - // for(int i = 0; (i < MAX_TRIES) && (freeId == -1); ++i) - // { - // freeId = (new Random()).nextInt(); - // if(containers.containsKey(freeId)) - // freeId = -1; - // else - // containers.put(freeId, container); - // } - // - // return freeId; - // } - - private boolean sendEmulatorConfig() - { - try { - emuConfig.sendAllTo(ctlSocket, emuCtlSocketName); - } - catch (Exception exception) { - LOG.warning("Sending configuration to emulator failed!"); - LOG.log(Level.SEVERE, exception.getMessage(), exception); - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - return false; - } + player = new SessionPlayerWrapper(file, headless); - return true; - } + return true; + } - private void ensureEmuCompState(EmuCompState expstate, String msgsuffix) throws BWFLAException - { - if (emuBeanState.get() != expstate) { - throw new BWFLAException("Expected state changed, abort waiting for " + msgsuffix + "!") - .setId(this.getComponentId()); - } - } + public int getSessionPlayerProgress() { + if (player == null) + return 0; - private void ensureEmulatorRunning(String msgsuffix) throws BWFLAException - { - if (!emuRunner.isProcessRunning()) { - throw new BWFLAException("Emulator failed, abort waiting for " + msgsuffix + "!") - .setId(this.getComponentId()); - } - } + return player.getProgress(); + } - private boolean waitForReadyNotification(int expevent, String message, int timeout, EmuCompState expstate) - { - LOG.info(message); + public boolean isReplayModeEnabled() { + if (player == null) + return false; - try { - final int waittime = 1000; // in ms - int numretries = (timeout > waittime) ? timeout / waittime : 1; - boolean isMsgAvailable = false; - while (numretries > 0) { - isMsgAvailable = ctlMsgReader.read(waittime); - if (isMsgAvailable) - break; + return player.isPlaying(); + } - this.ensureEmulatorRunning("notification"); + /* ==================== Monitoring API ==================== */ - if (emuBeanState.get() != expstate) { - LOG.warning("Expected state changed, abort waiting for notification!"); - return false; - } + @Override + public boolean updateMonitorValues() { + ProcessMonitor monitor = emuRunner.getProcessMonitor(); + if (monitor == null) { + // Process is currently not running! + return false; + } - --numretries; - } + return monitor.update(); + } - if (!isMsgAvailable) { - LOG.warning("Reading from emulator timed out!"); - return false; - } + @Override + public String getMonitorValue(ProcessMonitorVID id) { + ProcessMonitor monitor = emuRunner.getProcessMonitor(); + if (monitor == null) { + // Process is currently not running! + return ProcessMonitor.INVALID_VALUE; + } - if (!ctlMsgReader.isNotification()) { - LOG.warning("Received message was not a notification!"); - return false; - } + return monitor.getValue(id); + } - if (ctlMsgReader.getEventID() != expevent) { - LOG.warning("Received an unexpected notification from emulator!"); - return false; - } - } - catch (Exception exception) { - LOG.warning("Failed to read a notification-message from emulator."); - LOG.log(Level.SEVERE, exception.getMessage(), exception); - return false; - } + @Override + public List getMonitorValues(Collection ids) { + ProcessMonitor monitor = emuRunner.getProcessMonitor(); + if (monitor == null) { + // Process is currently not running! + return ProcessMonitor.INVALID_VALUE_LIST; + } - LOG.info("Received a ready-notification from emulator."); + return monitor.getValues(ids); + } - return true; - } + @Override + public List getAllMonitorValues() { + ProcessMonitor monitor = emuRunner.getProcessMonitor(); + if (monitor == null) { + // Process is currently not running! + return ProcessMonitor.INVALID_VALUE_LIST; + } - private void waitUntilEmulatorCtlSocketAvailable(EmuCompState expstate) throws BWFLAException - { - LOG.info("Waiting for emulator's control-socket to become available..."); + return monitor.getValues(); + } + + + /* ==================== PostScriptPrinter API ==================== */ + + public List getPrintJobs() throws BWFLAException { + if (printer == null) + return null; + + return printer.getPrintJobs(); + } + + /* ==================== Screenshot API ==================== */ + + public void takeScreenshot() { + if (scrshooter != null) + scrshooter.takeScreenshot(); + } + + public DataHandler getNextScreenshot() { + if (scrshooter == null) + return null; + + byte[] data = scrshooter.getNextScreenshot(); + if (data == null) + return null; + + return new DataHandler(data, "application/octet-stream"); + } + + + // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Utilities + // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // + // private int allocateContainerId(Container container) + // { + // int freeId = -1; + // final int MAX_TRIES = 50; + // for(int i = 0; (i < MAX_TRIES) && (freeId == -1); ++i) + // { + // freeId = (new Random()).nextInt(); + // if(containers.containsKey(freeId)) + // freeId = -1; + // else + // containers.put(freeId, container); + // } + // + // return freeId; + // } + + private boolean sendEmulatorConfig() { + try { + emuConfig.sendAllTo(ctlSocket, emuCtlSocketName); + } catch (Exception exception) { + LOG.warning("Sending configuration to emulator failed!"); + LOG.log(Level.SEVERE, exception.getMessage(), exception); + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + return false; + } - final Path socket = Paths.get(emuCtlSocketName); - final int timeout = 30000; // in ms - final int waittime = 1000; // in ms - for (int numretries = timeout / waittime; numretries > 0; --numretries) { - if (Files.exists(socket)) { - LOG.info("Emulator's control-socket is now available."); - return; - } + return true; + } - try { - Thread.sleep(waittime); - } - catch (Exception error) { - // Ignore it! - } + private void ensureEmuCompState(EmuCompState expstate, String msgsuffix) throws BWFLAException { + if (emuBeanState.get() != expstate) { + throw new BWFLAException("Expected state changed, abort waiting for " + msgsuffix + "!") + .setId(this.getComponentId()); + } + } - final String msgsuffix = "emulator's control-socket"; - this.ensureEmuCompState(expstate, msgsuffix); - this.ensureEmulatorRunning(msgsuffix); - } + private void ensureEmulatorRunning(String msgsuffix) throws BWFLAException { + if (!emuRunner.isProcessRunning()) { + throw new BWFLAException("Emulator failed, abort waiting for " + msgsuffix + "!") + .setId(this.getComponentId()); + } + } - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - throw new BWFLAException("Emulator's control socket is not available!") - .setId(this.getComponentId()); - } + private boolean waitForReadyNotification(int expevent, String message, int timeout, EmuCompState expstate) { + LOG.info(message); - private void waitUntilEmulatorCtlSocketReady(EmuCompState expstate) throws BWFLAException - { - final int timeout = 60000; // in ms + try { + final int waittime = 1000; // in ms + int numretries = (timeout > waittime) ? timeout / waittime : 1; + boolean isMsgAvailable = false; + while (numretries > 0) { + isMsgAvailable = ctlMsgReader.read(waittime); + if (isMsgAvailable) + break; - final String message = "Waiting for emulator's control-socket to become ready..."; - boolean ok = this.waitForReadyNotification(EventID.EMULATOR_CTLSOCK_READY, message, timeout, expstate); - if (!ok) { - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - throw new BWFLAException("Emulator's control socket is not reachable!") - .setId(this.getComponentId()); - } - } + this.ensureEmulatorRunning("notification"); - private boolean waitUntilEmulatorReady(EmuCompState expstate) throws BWFLAException - { - final int timeout = 30000; // in ms + if (emuBeanState.get() != expstate) { + LOG.warning("Expected state changed, abort waiting for notification!"); + return false; + } - final String message = "Waiting for emulator to become ready..."; - boolean ok = this.waitForReadyNotification(EventID.EMULATOR_READY, message, timeout, expstate); - if (!ok) { - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - throw new BWFLAException("Emulator was not started properly!") - .setId(this.getComponentId()); - } + --numretries; + } - return ok; - } + if (!isMsgAvailable) { + LOG.warning("Reading from emulator timed out!"); + return false; + } - private void waitUntilPathExists(Path path, EmuCompState expstate) throws BWFLAException - { - LOG.info("Waiting for path '" + path.toString() +"'..."); + if (!ctlMsgReader.isNotification()) { + LOG.warning("Received message was not a notification!"); + return false; + } - final int timeout = 180000; // in ms - final int waittime = 1000; // in ms - for (int numretries = timeout / waittime; numretries > 0; --numretries) { - if (Files.exists(path)) { - LOG.info("Path '" + path.toString() +"' exists now"); - return; - } + if (ctlMsgReader.getEventID() != expevent) { + LOG.warning("Received an unexpected notification from emulator!"); + return false; + } + } catch (Exception exception) { + LOG.warning("Failed to read a notification-message from emulator."); + LOG.log(Level.SEVERE, exception.getMessage(), exception); + return false; + } - try { - Thread.sleep(waittime); - } - catch (Exception error) { - // Ignore it! - } + LOG.info("Received a ready-notification from emulator."); - final String msgsuffix = "path"; - this.ensureEmuCompState(expstate, msgsuffix); - this.ensureEmulatorRunning(msgsuffix); - } + return true; + } - throw new BWFLAException("Path '" + path.toString() +"' does not exist!") - .setId(this.getComponentId()); - } - - private void waitUntilRestoreDone() - { - LOG.info("Waiting for CRIU restore-worker to exit..."); - - final ProcessRunner waiter = new ProcessRunner("/bin/sh"); - waiter.addArguments("-c", "while ! sudo runc " + - "ps " + this.getContainerId() + "; do :; done; while sudo runc ps " + this.getContainerId() + " | grep -q criu; do :; done"); - waiter.execute(); - } - - private void attachClientToEmulator() throws IOException, InterruptedException - { - LOG.info("Attaching client to emulator..."); - Thread.sleep(1000); - synchronized (ctlMsgWriter) { - ctlMsgWriter.begin(MessageType.ATTACH_CLIENT); - ctlMsgWriter.send(emuCtlSocketName); - } - } - - private void waitForAttachedClient() throws IOException, InterruptedException - { - final int timeout = 1000; - int numretries = 30; - - // Wait for the attached-event from emulator - while (numretries > 0) { - if (ctlEvents.await(EventID.CLIENT_ATTACHED, timeout)) { - LOG.info("Client attached to emulator."); - isClientAttachedFlag.set(true); - return; // Notification received! - } - - final EmuCompState state = emuBeanState.fetch(); - if (state != EmuCompState.EMULATOR_BUSY && state != EmuCompState.EMULATOR_RUNNING) { - LOG.warning("Expected state changed, abort attaching client to emulator!"); - return; - } + private void waitUntilEmulatorCtlSocketAvailable(EmuCompState expstate) throws BWFLAException { + LOG.info("Waiting for emulator's control-socket to become available..."); - --numretries; - } + final Path socket = Paths.get(emuCtlSocketName); + final int timeout = 30000; // in ms + final int waittime = 1000; // in ms + for (int numretries = timeout / waittime; numretries > 0; --numretries) { + if (Files.exists(socket)) { + LOG.info("Emulator's control-socket is now available."); + return; + } - throw new IOException("Attaching client to emulator failed!"); - } + try { + Thread.sleep(waittime); + } catch (Exception error) { + // Ignore it! + } - private void waitForClientDetachAck(long timeout, TimeUnit unit) throws IOException, InterruptedException - { - // Wait for the detached-event from emulator - if (!ctlEvents.await(EventID.CLIENT_DETACHED, unit.toMillis(timeout))) - throw new IOException("No detach-notification received from emulator!"); + final String msgsuffix = "emulator's control-socket"; + this.ensureEmuCompState(expstate, msgsuffix); + this.ensureEmulatorRunning(msgsuffix); + } - isClientAttachedFlag.set(false); - } + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + throw new BWFLAException("Emulator's control socket is not available!") + .setId(this.getComponentId()); + } - private String newCtlSocketName(String suffix) - { - return this.getSocketsDir() - .resolve("sdlonp-ctlsocket-" + suffix) - .toString(); - } + private void waitUntilEmulatorCtlSocketReady(EmuCompState expstate) throws BWFLAException { + final int timeout = 60000; // in ms - protected BWFLAException newNotSupportedException() - { - return new BWFLAException("Operation is not supported!") - .setId(this.getComponentId()); - } + final String message = "Waiting for emulator's control-socket to become ready..."; + boolean ok = this.waitForReadyNotification(EventID.EMULATOR_CTLSOCK_READY, message, timeout, expstate); + if (!ok) { + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + throw new BWFLAException("Emulator's control socket is not reachable!") + .setId(this.getComponentId()); + } + } - protected BWFLAException newNotImplementedException() - { - return new BWFLAException("Operation is not implemented!") - .setId(this.getComponentId()); - } + private boolean waitUntilEmulatorReady(EmuCompState expstate) throws BWFLAException { + final int timeout = 30000; // in ms - private BWFLAException newInitFailureException(String message, Throwable error) - { - emuBeanState.update(EmuCompState.EMULATOR_FAILED); - LOG.log(Level.SEVERE, message, error); - return new BWFLAException(message, error) - .setId(this.getComponentId()); - } + final String message = "Waiting for emulator to become ready..."; + boolean ok = this.waitForReadyNotification(EventID.EMULATOR_READY, message, timeout, expstate); + if (!ok) { + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + throw new BWFLAException("Emulator was not started properly!") + .setId(this.getComponentId()); + } - private ImageArchiveBinding findEmulatorImage(MachineConfiguration env) throws Exception - { - final var imageArchiveAddress = ConfigProvider.getConfig() - .getValue("rest.imagearchive", String.class); + return ok; + } + private void waitUntilPathExists(Path path, EmuCompState expstate) throws BWFLAException { + LOG.info("Waiting for path '" + path.toString() + "'..."); - //TODO REPLACE WITH CLIENT - return new ImageArchiveBinding(); + final int timeout = 180000; // in ms + final int waittime = 1000; // in ms + for (int numretries = timeout / waittime; numretries > 0; --numretries) { + if (Files.exists(path)) { + LOG.info("Path '" + path.toString() + "' exists now"); + return; + } + try { + Thread.sleep(waittime); + } catch (Exception error) { + // Ignore it! + } -// try (final var archive = ImageArchiveClient.create(imageArchiveAddress)) { -// final var emuMetaHelper = new EmulatorMetaHelperV2(archive, LOG); -// final var name = this.getEmuContainerName(env); -// var version = env.getEmulator() -// .getContainerVersion(); -// -// if (version == null || version.isEmpty()) -// version = EmulatorMetaData.DEFAULT_VERSION; -// -// LOG.info("Looking up image for emulator '" + name + " (" + version + ")'..."); -// final var emulator = emuMetaHelper.fetch(name, version); -// final var image = emulator.image(); -// final var binding = new ImageArchiveBinding(); -// binding.setId(EMUCON_ROOTFS_BINDING_ID); -// binding.setAccess(Binding.AccessType.COW); -// binding.setBackendName(this.getEmulatorArchive()); -// binding.setFileSystemType(image.fileSystemType()); -// binding.setType(image.category()); -// binding.setImageId(image.id()); -// binding.setUrl(""); -// -// LOG.info("Using emulator's image '" + image.id() + "'"); -// return binding; -// } -// catch (Exception error) { -// throw new BWFLAException("Emulator's image not found!", error) -// .setId(this.getComponentId()); -// } - } - - - /************************************************************************** - * - * Here be Bindings - * - **************************************************************************/ - - /** - * Resolves a binding location of either the form - * binding://binding_id[/path/to/subres] or binding_id[/path/to/subres]. The - * binding_id is replaced with the actual filesystem location of the - * binding's mountpoint. The possible reference to the subresource is - * preserved in the returned string. - * - * @param binding - * A binding location - * @return The resolved path or null, if the binding cannot - * be found - */ - protected String lookupResource(String binding) throws BWFLAException, IOException - { - String mountpoint = bindings.lookup(binding); - if (mountpoint == null) - mountpoint = bindings.mount(this.getComponentId(), binding, this.getBindingsDir()); - - return mountpoint; - } - - @Deprecated - protected String lookupResource(String binding, XmountOutputFormat unused) throws BWFLAException, IOException { - return lookupResource(binding); - } - - @Deprecated - protected String lookupResource(String binding, Drive.DriveType driveType) - throws BWFLAException, IOException { - // this.getImageFormatForDriveType(driveType) - return this.lookupResource(binding); - } - - protected void prepareResource(AbstractDataResource resource) throws IllegalArgumentException, IOException, BWFLAException - { - bindings.register(resource); - - // NOTE: Premount all object's entries to allow media-changes inside containers... - if (this.isContainerModeEnabled() && (resource instanceof ObjectArchiveBinding)) { - bindings.find(resource.getId() + "/") - .forEach((binding) -> { - try { - this.lookupResource(binding); - } - catch (Exception error) { - throw new IllegalArgumentException(error); - } - }); - } - } - - /************************************************************************** - * - * Here be Drives - * - **************************************************************************/ - - /** - * @param drive - */ - protected void prepareDrive(Drive drive) throws BWFLAException - { - // All drives *directly* work on a resource (binding) that has been - // set up earlier, so no mounting, cow-ing or other tricks - // are necessary here. - - if(drive.getData() == null || drive.getData().isEmpty()) - return; - - addDrive(drive); - - // String img = null; - // - // FIXME: check if this is still necessary after refactoring (if yes, - // refactor more) - // - // if (drive instanceof VolatileDrive) { - // // The drive should be written to in-place, ignoring the - // // value of getAccess(), as it is a temporary copy of user-data - // - // // (TODO) Currently only file: transport is allowed here - // if (!drive.getData().startsWith("file:")) { - // log. - // warning("Only 'file:' transport is allowed for injected objects/VolatileDrives."); - // continue; - // } - // // just use the file on the filesystem directly as is - // img = drive.getData().replace("file://", ""); - // } else { - - } - - protected abstract boolean addDrive(Drive drive) throws BWFLAException; - - protected abstract boolean connectDrive(Drive drive, boolean attach) throws BWFLAException; - - /************************************************************************** - * - * Here be Networks - * - **************************************************************************/ - - /** - * @param nic - */ - protected void prepareNic(Nic nic) throws BWFLAException, IOException - { - // create a vde_switch in hub mode - // the switch can later be identified using the NIC's MAC address - Path vdeHubName = this.getNetworksDir() - .resolve("nic_" + nic.getHwaddress()); - - if (this.isContainerModeEnabled()) { - // Pre-create switch-directory to be mounted into container! - Files.createDirectories(vdeHubName); - - // Compute switch-directory in container-space - final Path hostDataDir = this.getDataDir(); - final Path conDataDir = Paths.get(EMUCON_DATA_DIR); - vdeHubName = conDataDir.resolve(hostDataDir.relativize(vdeHubName)); - } - else { - ProcessRunner process = new ProcessRunner("vde_switch"); - process.addArgument("-hub"); - process.addArgument("-s"); - process.addArgument(vdeHubName.toString()); - if (!process.start()) - return; // Failure - - vdeProcesses.add(process); - } + final String msgsuffix = "path"; + this.ensureEmuCompState(expstate, msgsuffix); + this.ensureEmulatorRunning(msgsuffix); + } - this.addControlConnector(new EthernetConnector(nic.getHwaddress(), vdeHubName, LOG, this)); - this.addNic(nic); - } + throw new BWFLAException("Path '" + path.toString() + "' does not exist!") + .setId(this.getComponentId()); + } + + private void waitUntilRestoreDone() { + LOG.info("Waiting for CRIU restore-worker to exit..."); + + final ProcessRunner waiter = new ProcessRunner("/bin/sh"); + waiter.addArguments("-c", "while ! sudo runc " + + "ps " + this.getContainerId() + "; do :; done; while sudo runc ps " + this.getContainerId() + " | grep -q criu; do :; done"); + waiter.execute(); + } + + private void attachClientToEmulator() throws IOException, InterruptedException { + LOG.info("Attaching client to emulator..."); + Thread.sleep(1000); + synchronized (ctlMsgWriter) { + ctlMsgWriter.begin(MessageType.ATTACH_CLIENT); + ctlMsgWriter.send(emuCtlSocketName); + } + } + + private void waitForAttachedClient() throws IOException, InterruptedException { + final int timeout = 1000; + int numretries = 30; + + // Wait for the attached-event from emulator + while (numretries > 0) { + if (ctlEvents.await(EventID.CLIENT_ATTACHED, timeout)) { + LOG.info("Client attached to emulator."); + isClientAttachedFlag.set(true); + return; // Notification received! + } - protected abstract boolean addNic(Nic nic) throws BWFLAException; + final EmuCompState state = emuBeanState.fetch(); + if (state != EmuCompState.EMULATOR_BUSY && state != EmuCompState.EMULATOR_RUNNING) { + LOG.warning("Expected state changed, abort attaching client to emulator!"); + return; + } - /************************************************************************** - * - * Here be native config - * - **************************************************************************/ + --numretries; + } - /** - * @param nativeConfig - */ - protected void prepareNativeConfig(MachineConfiguration.NativeConfig nativeConfig) - { - if(nativeConfig != null) - { - String nativeString = nativeConfig.getValue(); - if(nativeConfig.getLinebreak() != null) - { - nativeString = nativeString.replace(nativeConfig.getLinebreak(), "\n"); - } + throw new IOException("Attaching client to emulator failed!"); + } + + private void waitForClientDetachAck(long timeout, TimeUnit unit) throws IOException, InterruptedException { + // Wait for the detached-event from emulator + if (!ctlEvents.await(EventID.CLIENT_DETACHED, unit.toMillis(timeout))) + throw new IOException("No detach-notification received from emulator!"); + + isClientAttachedFlag.set(false); + } + + private String newCtlSocketName(String suffix) { + return this.getSocketsDir() + .resolve("sdlonp-ctlsocket-" + suffix) + .toString(); + } + + protected BWFLAException newNotSupportedException() { + return new BWFLAException("Operation is not supported!") + .setId(this.getComponentId()); + } + + protected BWFLAException newNotImplementedException() { + return new BWFLAException("Operation is not implemented!") + .setId(this.getComponentId()); + } + + private BWFLAException newInitFailureException(String message, Throwable error) { + emuBeanState.update(EmuCompState.EMULATOR_FAILED); + LOG.log(Level.SEVERE, message, error); + return new BWFLAException(message, error) + .setId(this.getComponentId()); + } + + /** + * Used to find Emulator Image if EMUCON_ROOTFS_BINDING_ID -> emucon-rootfs is absent + * Using latest configuration + * + * @return ImageArchiveBinding + * @throws Exception + */ + private ImageArchiveBinding findEmulatorImage() throws Exception { + + Optional imageArchiveBindingDeclaration = BindingsResolver.findFirstImageArchiveBindingDeclaration(); + if (imageArchiveBindingDeclaration.isEmpty()) { + throw new BWFLAException(String.format("Emulator's image not found!")); + } - nativeString = nativeString.replace("\r", "") - .replaceAll("\n+", "\n"); + ImageArchiveBinding binding = imageArchiveBindingDeclaration.get(); + binding.setId(EMUCON_ROOTFS_BINDING_ID); + binding.setAccess(Binding.AccessType.COW); + binding.setBackendName(this.getEmulatorArchive()); + binding.setUrl(""); + + LOG.info("Using emulator's image '" + binding.getId() + "'"); + + return binding; + } + + + /************************************************************************** + * + * Here be Bindings + * + **************************************************************************/ + + /** + * Resolves a binding location of either the form + * binding://binding_id[/path/to/subres] or binding_id[/path/to/subres]. The + * binding_id is replaced with the actual filesystem location of the + * binding's mountpoint. The possible reference to the subresource is + * preserved in the returned string. + * + * @param binding A binding location + * @return The resolved path or null, if the binding cannot + * be found + */ + protected String lookupResource(String binding) throws BWFLAException, IOException { + String mountpoint = bindings.lookup(binding); + if (mountpoint == null) + mountpoint = bindings.mount(this.getComponentId(), binding, this.getBindingsDir()); + + return mountpoint; + } + + @Deprecated + protected String lookupResource(String binding, XmountOutputFormat unused) throws BWFLAException, IOException { + return lookupResource(binding); + } + + @Deprecated + protected String lookupResource(String binding, Drive.DriveType driveType) + throws BWFLAException, IOException { + // this.getImageFormatForDriveType(driveType) + return this.lookupResource(binding); + } + + protected void prepareResource(AbstractDataResource resource) throws IllegalArgumentException, IOException, BWFLAException { + bindings.register(resource); + + // NOTE: Premount all object's entries to allow media-changes inside containers... + if (this.isContainerModeEnabled() && (resource instanceof ObjectArchiveBinding)) { + bindings.find(resource.getId() + "/") + .forEach((binding) -> { + try { + this.lookupResource(binding); + } catch (Exception error) { + throw new IllegalArgumentException(error); + } + }); + } + } + + /************************************************************************** + * + * Here be Drives + * + **************************************************************************/ + + /** + * @param drive + */ + protected void prepareDrive(Drive drive) throws BWFLAException { + // All drives *directly* work on a resource (binding) that has been + // set up earlier, so no mounting, cow-ing or other tricks + // are necessary here. + + if (drive.getData() == null || drive.getData().isEmpty()) + return; + + addDrive(drive); + + // String img = null; + // + // FIXME: check if this is still necessary after refactoring (if yes, + // refactor more) + // + // if (drive instanceof VolatileDrive) { + // // The drive should be written to in-place, ignoring the + // // value of getAccess(), as it is a temporary copy of user-data + // + // // (TODO) Currently only file: transport is allowed here + // if (!drive.getData().startsWith("file:")) { + // log. + // warning("Only 'file:' transport is allowed for injected objects/VolatileDrives."); + // continue; + // } + // // just use the file on the filesystem directly as is + // img = drive.getData().replace("file://", ""); + // } else { + + } + + protected abstract boolean addDrive(Drive drive) throws BWFLAException; + + protected abstract boolean connectDrive(Drive drive, boolean attach) throws BWFLAException; + + /************************************************************************** + * + * Here be Networks + * + **************************************************************************/ + + /** + * @param nic + */ + protected void prepareNic(Nic nic) throws BWFLAException, IOException { + // create a vde_switch in hub mode + // the switch can later be identified using the NIC's MAC address + Path vdeHubName = this.getNetworksDir() + .resolve("nic_" + nic.getHwaddress()); + + if (this.isContainerModeEnabled()) { + // Pre-create switch-directory to be mounted into container! + Files.createDirectories(vdeHubName); + + // Compute switch-directory in container-space + final Path hostDataDir = this.getDataDir(); + final Path conDataDir = Paths.get(EMUCON_DATA_DIR); + vdeHubName = conDataDir.resolve(hostDataDir.relativize(vdeHubName)); + } else { + ProcessRunner process = new ProcessRunner("vde_switch"); + process.addArgument("-hub"); + process.addArgument("-s"); + process.addArgument(vdeHubName.toString()); + if (!process.start()) + return; // Failure + + vdeProcesses.add(process); + } - // search for binding:// and replace all occurrences with the - // actual path - // Pattern p = Pattern.compile("binding://(\\w*/?)"); - Pattern p = Pattern.compile("binding://(\\w*/?)|rom://(\\S+)"); - Matcher m = p.matcher(nativeString); - StringBuffer sb = new StringBuffer(); - while(m.find()) - { - String bindingPath; - try { - String res = m.group(1); - if(res == null) // should be a rom. but check again - { - if(!m.group(0).startsWith("rom")) - { - LOG.info("could not resolve resource: " + m.group(0)); - continue; - } - res = m.group(0); - } + this.addControlConnector(new EthernetConnector(nic.getHwaddress(), vdeHubName, LOG, this)); + this.addNic(nic); + } + + protected abstract boolean addNic(Nic nic) throws BWFLAException; + + /************************************************************************** + * + * Here be native config + * + **************************************************************************/ + + /** + * @param nativeConfig + */ + protected void prepareNativeConfig(MachineConfiguration.NativeConfig nativeConfig) { + if (nativeConfig != null) { + String nativeString = nativeConfig.getValue(); + if (nativeConfig.getLinebreak() != null) { + nativeString = nativeString.replace(nativeConfig.getLinebreak(), "\n"); + } - bindingPath = this.lookupResource(res.trim()); - } catch (Exception e) { - LOG.severe("lookupResource with " + m.group(1) + " failed."); - LOG.log(Level.SEVERE, e.getMessage(), e); - continue; - } - if(bindingPath == null) - { - LOG.severe("lookupResource with " + m.group(1) + " failed."); - continue; - } - LOG.info(m.group(1)); - LOG.info("Replacing " + m.group(0) + " by " + bindingPath); - m.appendReplacement(sb, bindingPath); - } - m.appendTail(sb); + nativeString = nativeString.replace("\r", "") + .replaceAll("\n+", "\n"); + + // search for binding:// and replace all occurrences with the + // actual path + // Pattern p = Pattern.compile("binding://(\\w*/?)"); + Pattern p = Pattern.compile("binding://(\\w*/?)|rom://(\\S+)"); + Matcher m = p.matcher(nativeString); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String bindingPath; + try { + String res = m.group(1); + if (res == null) // should be a rom. but check again + { + if (!m.group(0).startsWith("rom")) { + LOG.info("could not resolve resource: " + m.group(0)); + continue; + } + res = m.group(0); + } + + bindingPath = this.lookupResource(res.trim()); + } catch (Exception e) { + LOG.severe("lookupResource with " + m.group(1) + " failed."); + LOG.log(Level.SEVERE, e.getMessage(), e); + continue; + } + if (bindingPath == null) { + LOG.severe("lookupResource with " + m.group(1) + " failed."); + continue; + } + LOG.info(m.group(1)); + LOG.info("Replacing " + m.group(0) + " by " + bindingPath); + m.appendReplacement(sb, bindingPath); + } + m.appendTail(sb); - emuNativeConfig = sb.toString(); - } - } - - protected String getEmulatorWorkdir() - { - return null; - } - - public Tail getEmulatorStdOut() - { - LogConnector logCon = (LogConnector)getControlConnector(StdoutLogConnector.PROTOCOL); - if(logCon == null) - return null; - return logCon.connect(); - } - - public Tail getEmulatorStdErr() - { - LogConnector logCon = (LogConnector)getControlConnector(StderrLogConnector.PROTOCOL); - if(logCon == null) - return null; - return logCon.connect(); - } + emuNativeConfig = sb.toString(); + } + } + + protected String getEmulatorWorkdir() { + return null; + } + + public Tail getEmulatorStdOut() { + LogConnector logCon = (LogConnector) getControlConnector(StdoutLogConnector.PROTOCOL); + if (logCon == null) + return null; + return logCon.connect(); + } + + public Tail getEmulatorStdErr() { + LogConnector logCon = (LogConnector) getControlConnector(StderrLogConnector.PROTOCOL); + if (logCon == null) + return null; + return logCon.connect(); + } } diff --git a/emucon-tools/README.md b/emucon-tools/README.md new file mode 100644 index 0000000..d9f5961 --- /dev/null +++ b/emucon-tools/README.md @@ -0,0 +1,45 @@ +# Introduction + +`emucon-tools` is a collection of scripts for running containerized emulators. + + +# Bootstrapping + +The scripts and tools assume that `runtime/bin` folder is in your `PATH`. +To bootstrap a system for a single terminal session, simply run in your shell: +``` +$ . ./bootstrap.sh +``` + + +# Installation + +To install `emucon-tools` and all dependencies into `/usr/local`, simply run: +``` +$ ./install.sh --destination /usr/local +``` + +Some dependencies will be built from source and some installed using host's package manager. + +The previous command will also add a sudoers-configuration for the current user, to be able +to run required privileged commands without asking user password. A different user can be +specified with `--user ` option. For all available options, please run: +``` +$ ./install.sh --help +``` + +# Installation in Docker-Container + +The OCI tools must be built on the host (outside of Docker) by running: +``` +$ mkdir /tmp/oci-tools +$ ./installer/install-oci-tools.sh --destination /tmp/oci-tools +``` + +Then in your Dockerfile add: +``` +COPY /tmp/oci-tools /usr/local +RUN /installer/install-scripts.sh --destination /usr/local \ + && /installer/install-deps.sh +``` + diff --git a/emucon-tools/bootstrap.sh b/emucon-tools/bootstrap.sh new file mode 100755 index 0000000..0f636ac --- /dev/null +++ b/emucon-tools/bootstrap.sh @@ -0,0 +1,20 @@ +# +# Bootstrapping for emucon-tools +# + +curdir="$(pwd)" +bindir="${curdir}/runtime/bin" + +if [ ! -f "${bindir}/emucon-init.sh" ] ; then + echo 'Unexpected directory layout!' + echo 'Aborting...' +else + echo 'Modifying PATH for current terminal session...' + echo "Adding ${bindir}" + + export PATH="${bindir}:${PATH}" +fi + +unset bindir +unset curdir + diff --git a/emucon-tools/builder/commands/layer/layers/base/Dockerfile b/emucon-tools/builder/commands/layer/layers/base/Dockerfile new file mode 100644 index 0000000..76e0940 --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/Dockerfile @@ -0,0 +1,10 @@ +FROM phusion/baseimage:0.9.22 + +COPY scripts /emucon-scripts/ + +# Set up custom repo +COPY data/*.list /etc/apt/sources.list.d/ +COPY data/pin-bwfla.pref /etc/apt/preferences.d/pin-bwfla.pref + +RUN /sbin/my_init -- /emucon-scripts/install.sh + diff --git a/emucon-tools/builder/commands/layer/layers/base/data/bwfla.list b/emucon-tools/builder/commands/layer/layers/base/data/bwfla.list new file mode 100644 index 0000000..fec30e6 --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/data/bwfla.list @@ -0,0 +1 @@ +deb http://packages.eaas.uni-freiburg.de/ xenial bwfla diff --git a/emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref b/emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref new file mode 100644 index 0000000..cb9e1ad --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref @@ -0,0 +1,4 @@ +Package: * +Pin: release c=bwfla +Pin-Priority: 1001 + diff --git a/emucon-tools/builder/commands/layer/layers/base/data/xpra.list b/emucon-tools/builder/commands/layer/layers/base/data/xpra.list new file mode 100644 index 0000000..5a28ffd --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/data/xpra.list @@ -0,0 +1 @@ +deb https://xpra.org/ xenial main diff --git a/emucon-tools/builder/commands/layer/layers/base/run.sh b/emucon-tools/builder/commands/layer/layers/base/run.sh new file mode 100755 index 0000000..8cb34ac --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/run.sh @@ -0,0 +1,140 @@ +#!/bin/sh + +__cmd_name="${__cmd_name} base" + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} -t [-s ] -o + + DESCRIPTION: + Builds container's base layer. + + OPTIONS: + -t, --output-type + Output type for built layer: + tree: Filesystem tree + qcow: QCow container image + + -o, --output-path + Output path for built layer + + -s, --output-size + Output image size in bytes (with optional suffix K, M or G) + + -n, --use-nbd + Connect qcow-image to specified NBD device + + EOT +} + + +# ========== Script's Begin ========== + +. $(emucon-paths helpers.sh) + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse script's command line arguments +shortopts='t:o:s:n:h' +longopts='output-type:,output-path:,output-size:,use-nbd:,help' +cmdargs=$(emucon_parse_cmdargs -s "${shortopts}" -l "${longopts}" -- "$@") +if emucon_cmd_failed ; then + emucon_abort +fi + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while true ; do + case "$1" in + -t|--output-type) + outtype="$2" + shift 2 ;; + -o|--output-path) + outpath="$2" + shift 2 ;; + -s|--output-size) + outsize="$2" + shift 2 ;; + -n|--use-nbd) + nbdpath="$2" + shift 2 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v ;; + esac +done + +# Check arguments +emucon_check_required_arg "-t/--output-type" "${outtype}" +emucon_check_required_arg "-o/--output-path" "${outpath}" + +# Prepare output image/directory +case "${outtype}" in + tree) + emucon_ensure_dir_exists "${outpath}" + outdir="${outpath}" + ;; + qcow) + emucon_check_required_arg "-s/--output-size" "${outsize}" + emucon-qcow create -o "size=${outsize}" "${outpath}" || emucon_abort + emucon-qcow mkfs --fs-type ext4 "${outpath}" || emucon_abort + + emucon_print_info "Creating temporary mountpoints for qcow-image..." + tmpdir=$(mktemp -d --tmpdir 'emucon-XXXXX') + outdir="${tmpdir}/fs" + mkdir -v -p "${outdir}" + if [ -n "${nbdpath}" ] ; then + rawdir="${nbdpath}" + else + rawdir="${tmpdir}/raw" + mkdir -v -p "${rawdir}" + fi + + __cleanup() { + emucon_print_info 'Cleaning up...' + emucon-qcow unmount --fs-path "${outdir}" "${rawdir}" || emucon_abort + rm -r -v "${tmpdir}" + } + + trap __cleanup EXIT + + emucon-qcow mount --fs-type ext4 --fs-path "${outdir}" "${outpath}" "${rawdir}" || emucon_abort + ;; + *) + emucon_print_error "Invalid output type specified: ${outtype}" + emucon_abort -v + ;; +esac + +# Docker names +tag='eaas/emucon-base-layer:latest' +name='eaas-emucon-base' + +emucon_print_info "Building new image for '${tag}'..." +sudo docker build --no-cache --force-rm=true -t "${tag}" . || emucon_abort -v + +emucon_print_info "Creating container from image '${tag}'..." +container=$(sudo docker create --name "${name}" "${tag}") || emucon_abort -v + +emucon_print_info "Exporting image to ${outdir}" +excludes='--exclude=dev/* --exclude=proc/* --exclude=usr/share/doc/* --exclude=usr/share/man/* --exclude=*/__pycache__/*' +tarargs="--ignore-failed-read --totals -C ${outdir}" +sudo docker export "${container}" | tar ${excludes} ${tarargs} -xvf - + +emucon_print_info "Removing container ${name}" +sudo docker rm "${name}" + diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt b/emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt new file mode 100644 index 0000000..9e78691 --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt @@ -0,0 +1,13 @@ +# Common dependencies for supported emulators +# (for Debian-based distributions) +# +# NOTE: empty lines and lines starting +# with # will be skipped! + +libguac-client-sdlonp0 +libsdl1.2debian +qemu-utils +vde2 +xpra +python-pip + diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init b/emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init new file mode 100644 index 0000000..c6f1cce --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init @@ -0,0 +1,140 @@ +#!/bin/sh + +# +# Script for starting following processes: +# - VDE-Switches (optional) +# - XPRA-Server (optional) +# - Emulator +# + +readonly display=':7000' +export HOME='/home/bwfla' + + +# ========== Helper Functions ========== + +__abort() { + echo 'Aborting...' + exit 1 +} + +__list_nics() { + ls "$1" | grep 'nic' +} + +__wait_until_ready() { + local curiter='-1' + local delay='1s' + local service="$1" + local path="$2" + local maxiter="$3" + while [ ! -e "${path}" ] ; do + curiter=$((curiter + 1)) + if [ "${curiter}" -gt "${maxiter}" ] ; then + echo "==> FAILED: ${service}" + __abort + fi + sleep "${delay}" + done + echo "==> READY: ${service}" +} + +__on_exit() { + echo 'Waiting for main process to terminate...' + wait "${mpid}" + + echo 'Terminating background processes...' + kill -TERM -1 + + echo 'Waiting for background processes to terminate...' + while ! wait ; do + : ; + done + + if [ -n "${xprasock}" ] ; then + echo '===== XPRA LOG =========================' + cat "/run/user/$(id --user)/xpra/${display}.log" + fi +} + + +# ========== Script's Begin ========== + +# Parse command line arguments +cmdargs=$(getopt -o 'n:s:' -l 'networks-dir:,xpra-socket:' -n 'emucon-init' -- "$@") +if [ $? -ne 0 ] ; then + echo 'Parsing command arguments failed!' + __abort +fi + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while [ $# -gt 0 ] ; do + case "$1" in + -n|--networks-dir) + nicsdir="$2" + shift 1 ;; + -s|--xpra-socket) + xprasock="$2" + shift 1 ;; + --) + shift 1 + break ;; + *) + echo "Invalid command line arguments found: ${cmdargs}" + __abort ;; + esac + shift 1 +done + +if [ -z "${nicsdir}" ] || [ ! -d "${nicsdir}" ] ; then + echo "Required argument -n/--networks-dir is missing!" + __abort +fi + +trap __on_exit EXIT + +if [ -n "${xprasock}" ] ; then + echo 'Starting XPRA-daemon...' + xpra start ${display:?} \ + --start="sh -c 'xhost +si:localuser:bwfla; touch /tmp/xpra-started'" \ + --bind="${xprasock:?}" \ + --daemon=yes \ + --html=off + + echo '==> DONE: XPRA-daemon started' + cmdargs="--xpra-socket ${xprasock}" +fi + +# NOTE: We need to start a vde-hub for every NIC. +echo 'Starting VDE-hub processes...' +for nic in $(__list_nics "${nicsdir}") ; do + nicpath="${nicsdir}/${nic}" + vde_switch -hub -s "${nicpath}" -d + echo "==> DONE: ${nicpath}" +done + +echo 'Waiting for all VDE-hubs to be ready...' +for nic in $(__list_nics "${nicsdir}") ; do + __wait_until_ready "/etc/service/${nic}" "${nicsdir}/${nic}/ctl" '5' +done + +if [ -n "${xprasock}" ] ; then + export DISPLAY="${display:?}" + echo 'Waiting for XPRA-daemon to be ready...' + __wait_until_ready 'XPRA-daemon' '/tmp/xpra-started' '30' +fi + +__on_terminate() { + echo 'Terminating main process...' + kill -TERM "${mpid}" +} + +trap __on_terminate TERM + +echo 'Running main command...' +"$@" & + +readonly mpid="$!" +wait "${mpid}" + diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt b/emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt new file mode 100644 index 0000000..32b7bbd --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt @@ -0,0 +1,21 @@ +# Package names for supported emulators +# (for Debian-based distributions) +# +# NOTE: empty lines and lines starting +# with # will be skipped! + +fs-uae +hatari +sheepshaver +beebem +kegs-sdl +vice-sdl +basilisk2 +pce-atari-st +pce-dos +pce-ibmpc +pce-macplus +dosbox +qemu-system-x86 +qemu-system-ppc + diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/install.sh b/emucon-tools/builder/commands/layer/layers/base/scripts/install.sh new file mode 100755 index 0000000..24520c9 --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/base/scripts/install.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +# Directory containing scripts +readonly scripts=$(dirname $(readlink -f $0)) + +# ========== Internal Helpers ========== + +__print_message() +{ + echo "[container] $1" +} + +__to_flat_list() +{ + # Skip all commented out and empty lines + cat - | grep --invert-match -E '#|^$' | tr '\n' ' ' +} + +__print_emulators() +{ + cat "${scripts}/emulators.txt" | __to_flat_list +} + +__print_dependencies() +{ + cat "${scripts}/dependencies.txt" | __to_flat_list +} + + +# ========== Script's Begin ========== + +__print_message "Executing script $0..." + +export DEBIAN_FRONTEND=noninteractive + +__print_message 'Upgrading system packages...' +apt-get update +apt-get upgrade -y +apt-get autoremove + +__print_message 'Installing emulators and deps...' +readonly emulators="$(__print_emulators)" +readonly deps="$(__print_dependencies)" +apt-get install -y --allow-unauthenticated ${deps} ${emulators} +apt-get autoremove + +__print_message 'Installing xpra deps...' +pip install python-uinput + +__print_message 'Removing emulators...' +apt-get remove -y --allow-unauthenticated ${emulators} + +__print_message 'Installing emucon-init...' +install -v -m 'a=rx' "${scripts}/emucon-init" '/usr/bin/emucon-init' + +# NOTE: apt-get tool tries to drop privileges, when run as root. +# For this a user _apt is created inside of the container. +# But when we export and run containers inside a user-namespace, +# apt-get tool fails to change the owner of some temporary folders +# with "Permission denied" errors. This happens because the host +# user normally doesn't have the permissions to change owners of +# folders without elevated privileges. + +# HACK: change apt-tool's user ID to root +usermod --non-unique --uid 0 _apt + +# Add main user +readonly uid='1000' +addgroup --gid "${uid}" bwfla +useradd -ms /bin/bash --uid "${uid}" --gid bwfla bwfla +adduser bwfla xpra + +__print_message 'Update Xpra config...' +sed -i '$s/$/ -nolisten local/' /etc/xpra/conf.d/55_server_x11.conf +sed -i '$s/-auth *[^ ]*//' /etc/xpra/conf.d/55_server_x11.conf + +# Xpra runtime directory +mkdir -p "/run/user/${uid}/xpra" +chmod a=rwx "/run/user/${uid}/xpra" + +__print_message 'Cleaning up package manager...' +apt-get clean + +__print_message 'Cleaning up directories...' +rm -rf /var/lib/apt/lists/* +rm -rf /var/tmp/* +rm -rf /tmp/* +rm -rf "${scripts}" + +__print_message "Script $0 finished" + diff --git a/emucon-tools/builder/commands/layer/layers/emulator/run.sh b/emucon-tools/builder/commands/layer/layers/emulator/run.sh new file mode 100755 index 0000000..8911bb7 --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/emulator/run.sh @@ -0,0 +1,194 @@ +#!/bin/sh + +__cmd_name="${__cmd_name} emulator" + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} -t -o -b [...] + + DESCRIPTION: + Builds container's emulator layer. + + OPTIONS: + -t, --output-type + Output type for built layer: + tree: Filesystem tree + qcow: QCow container image + + -o, --output-dir + Output directory for built files. + + -b, --base-layer + Base layer directory or qcow-image. + + -n, --use-nbd + Connect qcow-image to specified NBD device. + + --list + List all supported emulator package names. + + ARGUMENTS: + + The emulator's package name. + + EOT +} + +# All supported emulators +__print_emulator_names() +{ + # Skip all commented out and empty lines + cat "$(emucon_get_current_dir)/../base/scripts/emulators.txt" \ + | grep --invert-match -E '#|^$' +} + +__check_emulator_name() +{ + # Find first match and stop immediately + __print_emulator_names | grep -m 1 --line-regexp "$1" > /dev/null +} + + +# ========== Script's Begin ========== + +. $(emucon-paths helpers.sh) + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse script's command line arguments +shortopts='t:o:b:n:h' +longopts='output-type:,output-dir:,base-layer:,use-nbd:,list,help' +cmdargs=$(emucon_parse_cmdargs -s "${shortopts}" -l "${longopts}" -- "$@") || emucon_abort + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while true ; do + case "$1" in + -b|--base-layer) + basepath="$2" + shift 2 ;; + -t|--output-type) + outtype="$2" + shift 2 ;; + -o|--output-dir) + outdir="$2" + shift 2 ;; + -n|--use-nbd) + nbdpath="$2" + shift 2 ;; + --list) + __print_emulator_names + emucon_exit ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v ;; + esac +done + +# Check arguments +emucon_check_required_arg "-t/--output-type" "${outtype}" +emucon_check_required_arg "-o/--output-dir" "${outdir}" +emucon_check_required_arg "-b/--base-layer" "${basepath}" +emucon_ensure_dir_exists "${outdir}" +case "${outtype}" in + tree) + emucon_ensure_dir_exists "${basepath}" + ;; + qcow) + emucon_ensure_file_exists "${basepath}" + ;; + *) + emucon_print_error "Invalid output type specified: ${outtype}" + emucon_abort -v + ;; +esac + +emulators='' +if [ $# -gt 0 ] ; then + # Check specified emulator names + for emulator in "$@" ; do + if ! __check_emulator_name "${emulator}" ; then + emucon_print_error "Invalid emulator's package name specified: ${emulator}" + emucon_abort -v + fi + emulators="${emulators} ${emulator}" + done +else + # No emulators specified, use all + emucon_print_warning 'No emulators specified! Build all supported.' + emulators="$(__print_emulator_names | tr '\n' ' ')" +fi + +# User for user-namespace mapping +user=$(id --user --name) +group=$(id --group --name) + +# Path for scripts to be run in containers +scripts="$(emucon_get_current_dir)/scripts" + +emucon_print_info 'Creating temporary directory...' +tmpdir=$(mktemp -d --tmpdir 'emucon-XXXXX') + +# Generate requested layers... +for emulator in ${emulators} ; do + emucon_print_info "Starting emulator-layer generation for ${emulator}..." + workdir="${tmpdir}/${emulator}" + mkdir -p "${workdir}" + case "${outtype}" in + tree) + emuoutpath="${outdir}/${emulator}" + mkdir -p "${emuoutpath}" + ;; + qcow) + emuoutpath="${outdir}/${emulator}.qcow" + emucon-qcow create -o "backing_file=${basepath}" "${emuoutpath}" + ;; + esac + + emucon_print_info "Using working directory: ${workdir}" + + emucon_print_info "Generating config.json for ${emulator}..." + emucon-cgen --mount "${scripts}:/emucon-scripts:bind:ro" \ + -- /sbin/my_init -- /emucon-scripts/install.sh "${emulator}" \ + > "${workdir}/config.json" + + emucon_print_info "Running container for ${emulator}..." + case "${outtype}" in + tree) + options="-t overlay -l ${basepath} -u ${emuoutpath}" ;; + qcow) + options="-t qcow -i ${emuoutpath}" + if [ -n "${nbdpath}" ] ; then + options="${options} --use-nbd ${nbdpath}" + fi + ;; + esac + + conid=$(mktemp --dry-run "congen-XXXXX") + if emucon-run -w "${workdir}" ${options} -c "${conid}" ; then + emucon_print_info "Emulator-layer for ${emulator} created at: ${emuoutpath}" + else + emucon_print_error "Generating emulator-layer for ${emulator} failed!" + fi + + emucon_print_info 'Cleaning up...' + rm -v -r "${workdir}" +done + +emucon_print_info 'Final cleanup...' +rm -v -r "${tmpdir}" + diff --git a/emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh b/emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh new file mode 100755 index 0000000..483d955 --- /dev/null +++ b/emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Directory containing scripts +readonly scripts=$(dirname $(readlink -f $0)) + + +# ========== Internal Helpers ========== + +__print_message() +{ + echo "[container] $1" +} + + +# ========== Script's Begin ========== + +__print_message "Executing script $0..." + +if [ $# -eq 0 ] ; then + __print_message 'No packages specified!' + __print_message "Usage: $0 [...]" + exit 1 +fi + +export DEBIAN_FRONTEND=noninteractive + +__print_message 'Installing packages...' +apt-get update +apt-get install -y --allow-unauthenticated "$@" + +__print_message 'Cleaning up package manager...' +apt-get clean + +__print_message 'Cleaning up directories...' +rm -rf /var/lib/apt/lists/* +rm -rf /var/tmp/* +rm -rf /tmp/* + +__print_message "Script $0 finished" + diff --git a/emucon-tools/builder/commands/layer/run.sh b/emucon-tools/builder/commands/layer/run.sh new file mode 100755 index 0000000..2f9a706 --- /dev/null +++ b/emucon-tools/builder/commands/layer/run.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +export __cmd_name="${__cmd_name} layer" + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} [...] + + DESCRIPTION: + Builds the specified container layer. + + ARGUMENTS: + + The name of the layer to build: + base - Base layer + emulator - Emulator layers + + EOT +} + + +# ========== Script's Begin ========== + +. $(emucon-paths helpers.sh) + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse script's command line arguments +case "$1" in + -h|--help) + __print_usage + emucon_exit ;; + base|emulator) + layer="$1" + shift 1 ;; + -*) + emucon_print_error "${__cmd_name}: unrecognized option '$1'" + emucon_print_cmdargs_parsing_error + emucon_abort -v ;; + *) + emucon_print_error "${__cmd_name}: invalid name -- '$1'" + emucon_print_cmdargs_parsing_error + emucon_abort -v ;; +esac + +# Running subcommand +cd "layers/${layer}" +./run.sh "$@" + diff --git a/emucon-tools/builder/commands/tool/run.sh b/emucon-tools/builder/commands/tool/run.sh new file mode 100755 index 0000000..810d016 --- /dev/null +++ b/emucon-tools/builder/commands/tool/run.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +__cmd_name="${__cmd_name} tool" + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} -o [...] + + DESCRIPTION: + Builds the specified OCI tool(s) from sources. + + ARGUMENTS: + + The name of the tool to build: + image-tools - OCI image-tools + runtime-tools - OCI runtime-tools + runc - OCI runc-tool + + OPTIONS: + -o, --output-dir + Output directory for built files. + + EOT +} + + +# ========== Script's Begin ========== + +. emucon-init.sh + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse script's command line arguments +shortopts='o:h' +longopts='output-dir:,help' +cmdargs=$(emucon_parse_cmdargs -s "${shortopts}" -l "${longopts}" -- "$@") +if emucon_cmd_failed ; then + emucon_abort +fi + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while true ; do + case "$1" in + -o|--output-dir) + outdir="$2" + shift 2 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v ;; + esac +done + +# Tools to build +tools="$@" +if [ -z "${tools}" ] ; then + tools='image-tools runtime-tools runc' + emucon_print_warning "No tools specified! Building all: ${tools}" +else + # Check tool names + for tool in ${tools} ; do + case "${tool}" in + image-tools|runtime-tools|runc) + # Valid name + ;; + *) + emucon_print_error "Invalid tool name specified: ${tool}" + emucon_abort -v ;; + esac + done +fi + +emucon_check_required_arg '-o/--output-dir' "${outdir}" +emucon_ensure_dir_exists "${outdir}" + +# Docker args +image='ubuntu:18.04' +dstdir='/emucon-scripts' +scriptvol="$(emucon_get_current_dir)/scripts:${dstdir}:ro" +outvol="${outdir}:/emucon-output" +cmd="${dstdir}/build.sh ${tools}" + +emucon_print_info 'Running container for building...' +sudo docker run -t --rm -v "${scriptvol}" -v "${outvol}" "${image}" ${cmd} +emucon_print_info "Built files copied to: ${outdir}" + diff --git a/emucon-tools/builder/commands/tool/scripts/build-image-tools.sh b/emucon-tools/builder/commands/tool/scripts/build-image-tools.sh new file mode 100644 index 0000000..88c0d51 --- /dev/null +++ b/emucon-tools/builder/commands/tool/scripts/build-image-tools.sh @@ -0,0 +1,21 @@ +# +# Builds image-tools binaries +# + +__print_message 'Downloading OCI image-tools...' +dstdir="${GOPATH}/src/github.com/opencontainers/image-tools" +url='https://github.com/opencontainers/image-tools.git' +tag='v1.0.0-rc3' +mkdir -v -p "${dstdir}" +cd "${dstdir}" +git clone --depth 1 --branch "${tag}" "${url}" . + +__print_message 'Building OCI image-tools...' +go mod init +go mod tidy +go mod vendor +make tool man + +__print_message 'Installing OCI image-tools...' +make install PREFIX="${HOME}/.local" BINDIR="${GOBIN}" + diff --git a/emucon-tools/builder/commands/tool/scripts/build-runc.sh b/emucon-tools/builder/commands/tool/scripts/build-runc.sh new file mode 100644 index 0000000..8acdbc7 --- /dev/null +++ b/emucon-tools/builder/commands/tool/scripts/build-runc.sh @@ -0,0 +1,18 @@ +# +# Builds runc binary +# + +__print_message 'Downloading OCI runc...' +dstdir="${GOPATH}/src/github.com/opencontainers/runc" +url='https://github.com/opencontainers/runc.git' +tag='v1.1.4' +mkdir -v -p "${dstdir}" +cd "${dstdir}" +git clone --depth 1 --branch "${tag}" "${url}" . + +__print_message 'Building OCI runc...' +make BUILDTAGS='' + +__print_message 'Installing OCI runc...' +make install PREFIX="${HOME}/.local" BINDIR="${GOBIN}" + diff --git a/emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh b/emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh new file mode 100644 index 0000000..2c4210d --- /dev/null +++ b/emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh @@ -0,0 +1,21 @@ +# +# Builds runtime-tools binaries +# + +__print_message 'Downloading OCI runtime-tools...' +dstdir="${GOPATH}/src/github.com/opencontainers/runtime-tools" +url='https://github.com/opencontainers/runtime-tools.git' +tag='v0.9.0' +mkdir -v -p "${dstdir}" +cd "${dstdir}" +git clone --depth 1 --branch "${tag}" "${url}" . + +__print_message 'Building OCI runtime-tools...' +go mod init +go mod tidy +go mod vendor +make tool man + +__print_message 'Installing OCI runtime-tools...' +make install PREFIX="${HOME}/.local" BINDIR="${GOBIN}" + diff --git a/emucon-tools/builder/commands/tool/scripts/build.sh b/emucon-tools/builder/commands/tool/scripts/build.sh new file mode 100755 index 0000000..d37a319 --- /dev/null +++ b/emucon-tools/builder/commands/tool/scripts/build.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +set -e + +__print_message() +{ + echo "[container] $1" +} + +__print_message "Executing script $0..." + +# Tools to build +if [ -z "$*" ]; then + __print_message 'No tools spcecified! Exiting...' + exit 1 +fi + +readonly outdir='/emucon-output' +readonly scripts="$(dirname $(readlink -f $0))" + +. "${scripts}/prepare-go.sh" + +# Build all requested tools +for tool in "$@" ; do + . "${scripts}/build-${tool}.sh" +done + +__print_message 'Copying output files...' +cp -v -r -t "${outdir}" ${HOME}/.local/* + +__print_message "Script $0 finished" + diff --git a/emucon-tools/builder/commands/tool/scripts/prepare-go.sh b/emucon-tools/builder/commands/tool/scripts/prepare-go.sh new file mode 100644 index 0000000..c2828e9 --- /dev/null +++ b/emucon-tools/builder/commands/tool/scripts/prepare-go.sh @@ -0,0 +1,19 @@ +# +# Prepares GO toolchain +# + +__print_message 'Installing GO toolchain...' +apt-get update +apt-get install -y git curl golang go-md2man +curl -L "https://go.dev/dl/go1.19.linux-amd64.tar.gz" | tar xz -C /usr/local + +__print_message 'Setting up GO toolchain...' +mkdir -p "${HOME}/.local/bin" +cat >> "${HOME}/.profile" <<- "EOF" + export GOPATH=${HOME}/work + export GOBIN=${HOME}/.local/bin + export PATH="/usr/local/go/bin:${GOBIN}:${PATH}" + export MANPATH=/usr/local/go/share/man:${HOME}/.local/share/man:"${MANPATH}" +EOF + +. "${HOME}/.profile" diff --git a/emucon-tools/builder/emucon-build b/emucon-tools/builder/emucon-build new file mode 100755 index 0000000..aee2dca --- /dev/null +++ b/emucon-tools/builder/emucon-build @@ -0,0 +1,57 @@ +#!/bin/sh + +export __cmd_name=$(basename $0) + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} [...] + + DESCRIPTION: + Builds the specified tools or container layers. + + ARGUMENTS: + + Name of the command to execute: + layer - Build a container layer + tool - Build an OCI tool + + EOT +} + + +# ========== Script's Begin ========== + +. emucon-init.sh + +if [ $# -eq 0 ]; then + __print_usage + emucon_exit +fi + +# Parse script's command line arguments +case "$1" in + -h|--help) + __print_usage + emucon_exit ;; + layer|tool) + cmd="$1" + shift 1 ;; + -*) + emucon_print_error "${__cmd_name}: unrecognized option '$1'" + emucon_print_cmdargs_parsing_error + emucon_abort -v ;; + *) + emucon_print_error "${__cmd_name}: invalid command -- '$1'" + emucon_print_cmdargs_parsing_error + emucon_abort -v ;; +esac + +# Running subcommand +cd "commands/${cmd}" +./run.sh "$@" + diff --git a/emucon-tools/install.sh b/emucon-tools/install.sh new file mode 100755 index 0000000..5182095 --- /dev/null +++ b/emucon-tools/install.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +__cmd_name=$(basename "$0") + +# Default install directory +readonly default_dstdir='/usr/local' + +# Default sudoers directory +readonly default_sudodir='/etc/sudoers.d' + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} [-u ] [--sudoers-dir ] [--destination ] + + DESCRIPTION: + Installs emucon-tools and dependencies on the host system. + + OPTIONS: + -d, --destination + The path to install into. (default: ${default_dstdir}) + + -u, --user + The name of the user for sudoers configuration. + + --sudoers-dir + The path for sudoers drop-in files. (default: ${default_sudodir}) + + EOT +} + + +# ========== Script's Begin ========== + +if ! which emucon-install > /dev/null ; then + echo 'Required emucon-tools are not in PATH!' + echo 'Please source bootstrap.sh first.' + echo 'Aborting...' + exit 1 +fi + +. emucon-init.sh + +# Parse script's command line arguments +longopts='destination:,user:,sudoers-dir:,help' +cmdargs=$(emucon_parse_cmdargs -s 'd:u:h' -l "${longopts}" -- "$@") +if emucon_cmd_failed ; then + emucon_abort +fi + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while true ; do + case "$1" in + -d|--destination) + dstdir="$2" + shift 2 ;; + -u|--user) + user="$2" + shift 2 ;; + --sudoers-dir) + sudodir="$2" + shift 2 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v ;; + esac +done + +# Safety check! +if [ $# -ne 0 ] ; then + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v +fi + +# Installer scripts directory +srcdir=$(emucon_get_current_dir "$0")/installer + +# Install directory +dstdir="${dstdir:-${default_dstdir}}" +emucon_ensure_dir_exists "${dstdir}" + +# Sudoers directory +sudodir="${sudodir:-${default_sudodir}}" +emucon_ensure_dir_exists "${sudodir}" + +# Sudoers user +user="${user:-$(id --user --name)}" + +${srcdir}/install-oci-tools.sh --destination "${dstdir}" || emucon_abort +${srcdir}/install-scripts.sh --user "${user}" --sudoers-dir "${sudodir}" --destination "${dstdir}" || emucon_abort +${srcdir}/install-deps.sh || emucon_abort + diff --git a/emucon-tools/installer/install-deps.sh b/emucon-tools/installer/install-deps.sh new file mode 100755 index 0000000..d93aa14 --- /dev/null +++ b/emucon-tools/installer/install-deps.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +__cmd_name=$(basename "$0") + + +# ========== Script's Begin ========== + +. emucon-init.sh + +emucon_print_info 'Installing dependencies...' +sudo apt-get update +sudo apt-get install jq + diff --git a/emucon-tools/installer/install-oci-tools.sh b/emucon-tools/installer/install-oci-tools.sh new file mode 100755 index 0000000..960ccba --- /dev/null +++ b/emucon-tools/installer/install-oci-tools.sh @@ -0,0 +1,96 @@ +#!/bin/sh + +__cmd_name=$(basename "$0") + +# Default install directory +readonly default_dstdir='/usr/local' + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} [--destination ] + + DESCRIPTION: + Builds and installs OCI-Tools. + + OPTIONS: + -d, --destination + The path to install into. (default: ${default_dstdir}) + + EOT +} + + +# ========== Script's Begin ========== + +if ! which emucon-install > /dev/null ; then + echo 'Required emucon-tools are not in PATH!' + echo 'Please source bootstrap.sh first.' + echo 'Aborting...' + exit 1 +fi + +. emucon-init.sh + +# Parse script's command line arguments +longopts='destination:,help' +cmdargs=$(emucon_parse_cmdargs -s 'd:h' -l "${longopts}" -- "$@") +if emucon_cmd_failed ; then + emucon_abort +fi + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while true ; do + case "$1" in + -d|--destination) + dstdir="$2" + shift 2 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v ;; + esac +done + +# Safety check! +if [ $# -ne 0 ] ; then + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v +fi + +# Runtime directory +curdir=$(emucon_get_current_dir "$0") + +# Install directory +dstdir="${dstdir:-${default_dstdir}}" +emucon_ensure_dir_exists "${dstdir}" + +emucon_print_info 'Creating temporary directory...' +readonly tmpdir=$(mktemp -d '/tmp/emucon-XXX') + +__cleanup() +{ + emucon_print_info 'Cleaning up...' + sudo rm -r -v "${tmpdir}" +} + +# Setup an exit-trap +trap __cleanup EXIT + +emucon_print_info 'Building OCI tools...' +cd "${curdir}/../builder" +./emucon-build tool --output-dir "${tmpdir}" || emucon_abort + +emucon_print_info 'Installing OCI tools...' +emucon-install "${tmpdir}" "${dstdir}" || emucon_abort + diff --git a/emucon-tools/installer/install-scripts.sh b/emucon-tools/installer/install-scripts.sh new file mode 100755 index 0000000..50d89b8 --- /dev/null +++ b/emucon-tools/installer/install-scripts.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +__cmd_name=$(basename "$0") + +# Default install directory +readonly default_dstdir='/usr/local' + +# Default sudoers directory +readonly default_sudodir='/etc/sudoers.d' + + +# ========== Helper Functions ========== + +__print_usage() +{ + cat <<- EOT + USAGE: + ${__cmd_name} [-u ] [--sudoers-dir ] [--destination ] + + DESCRIPTION: + Installs emucon-tools and dependencies on the host system. + + OPTIONS: + -d, --destination + The path to install into. (default: ${default_dstdir}) + + -u, --user + The name of the user for sudoers configuration. + + --sudoers-dir + The path for sudoers drop-in files. (default: ${default_sudodir}) + + EOT +} + + +# ========== Script's Begin ========== + +if ! which emucon-install > /dev/null ; then + echo 'Required emucon-tools are not in PATH!' + echo 'Please source bootstrap.sh first.' + echo 'Aborting...' + exit 1 +fi + +. emucon-init.sh + +# Parse script's command line arguments +longopts='destination:,user:,sudoers-dir:,help' +cmdargs=$(emucon_parse_cmdargs -s 'd:u:h' -l "${longopts}" -- "$@") +if emucon_cmd_failed ; then + emucon_abort +fi + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while true ; do + case "$1" in + -d|--destination) + dstdir="$2" + shift 2 ;; + -u|--user) + user="$2" + shift 2 ;; + --sudoers-dir) + sudodir="$2" + shift 2 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v ;; + esac +done + +# Safety check! +if [ $# -ne 0 ] ; then + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort -v +fi + +# Runtime directory +curdir=$(emucon_get_current_dir) +srcdir=$(emucon_to_absolute_path "${curdir}/../runtime") + +# Install directory +dstdir="${dstdir:-${default_dstdir}}" +emucon_ensure_dir_exists "${dstdir}" + +# Sudoers directory +sudodir="${sudodir:-${default_sudodir}}" +emucon_ensure_dir_exists "${sudodir}" + +# Sudoers user +user="${user:-$(id --user --name)}" + +emucon_print_info 'Creating temporary directory...' +readonly tmpdir=$(mktemp -d '/tmp/emucon-XXX') + +__cleanup() +{ + emucon_print_info 'Cleaning up...' + sudo rm -r -v "${tmpdir}" +} + +# Setup an exit-trap +trap __cleanup EXIT + +emucon_print_info 'Installing emucon-tools...' +emucon-install "${srcdir}" "${dstdir}" || emucon_abort + +emucon_print_info "Installing sudoers configuration for user '${user}'..." +sudosrc="$(emucon-paths sudoers.template)" +sudotmp="${tmpdir}/emucon-pwdless-commands" +sedcmds="s|{{install-dir}}|${dstdir}|g; s|{{user}}|${user}|g" +cat "${sudosrc}" | sed "${sedcmds}" > "${sudotmp}" || emucon_abort +visudo --check --file "${sudotmp}" || emucon_abort +if [ ! -w "${sudodir}" ] ; then + # Install directory not writable, + # use sudo for file operations + cmdprefix='sudo' +fi + +# TODO: skip installing it for now! A different solution is needed! +#${cmdprefix} install -v -m 440 "${sudotmp}" "${sudodir}" || emucon_abort diff --git a/emucon-tools/runtime/lib/emucon/helpers.sh b/emucon-tools/runtime/lib/emucon/helpers.sh new file mode 100644 index 0000000..6b1ec90 --- /dev/null +++ b/emucon-tools/runtime/lib/emucon/helpers.sh @@ -0,0 +1,175 @@ +# +# Helpers for emucon-tools, compatible with /bin/sh +# + +# ========== Helper Functions ========== + +emucon_exit() +{ + exit 0 +} + +emucon_abort() +{ + if [ "$1" = '-v' ] ; then + emucon_print_error 'Aborting...' + fi + + exit 1 +} + +emucon_cmd_failed() +{ + # Failed, if exit code != 0 + test $? -ne 0 +} + +emucon_print() +{ + echo "$*" +} + +emucon_print_info() +{ + echo "[${__cmd_name}] $*" +} + +emucon_print_warning() +{ + if [ -n "${TERM}" ] ; then + # Output in yellow color! + tput setaf 3 + emucon_print_info "$@" + tput sgr0 + else + emucon_print_info "$@" + fi +} + +emucon_print_error() +{ + if [ -n "${TERM}" ] ; then + # Output in red color! + tput setaf 1 + emucon_print_info "$@" >&2 + tput sgr0 + else + emucon_print_info "$@" >&2 + fi +} + +emucon_print_invalid_cmdargs_error() +{ + emucon_print_error "Invalid command line arguments: $1" +} + +emucon_print_cmdargs_parsing_error() +{ + emucon_print_error 'Parsing command line arguments failed!' + emucon_print_error "Run '${__cmd_name} --help' for usage documentation." +} + +emucon_parse_cmdargs() +{ + # Example call: + # emucon_parse_cmdargs -s 'o:h' -l 'output:,help' -- args + + local shortopts + local longopts + + # Parse function's named arguments + while true ; do + case "$1" in + -s|--short) + shortopts="$2" + shift 2 ;; + -l|--long) + longopts="$2" + shift 2 ;; + --) + shift 1 + break ;; + *) + emucon_print_error "emucon_parse_cmdargs: unrecognized option '$1'" + emucon_print_error 'Internal error occured! Aborting...' + emucon_abort + esac + done + + local cmdargs + + # Parse supplied command's arguments + cmdargs=$(getopt -o "${shortopts}" -l "${longopts}" -n "${__cmd_name}" -- "$@") + if emucon_cmd_failed ; then + emucon_print_cmdargs_parsing_error + emucon_abort -v + fi + + printf "%s" "${cmdargs}" +} + +emucon_check_required_arg() +{ + local argname + local argvalue + + argname="$1" + argvalue="$2" + + if [ -z "${argvalue}" ] ; then + emucon_print_error "Required argument ${argname} is missing!" + emucon_abort -v + fi +} + +emucon_to_absolute_path() +{ + if [ -f "$1" ] ; then + local dname="$(dirname "$1")" + local fname="/$(basename "$1")" + else + local dname="$1" + fi + + dname="$(cd "${dname}" && pwd)" + printf '%s%s\n' "${dname}" "${fname}" +} + +emucon_to_absolute_dirname() +{ + echo $(dirname $(emucon_to_absolute_path "$1")) +} + +emucon_get_current_dir() +{ + echo $(emucon_to_absolute_dirname "$0") +} + +emucon_ensure_dir_exists() +{ + if [ ! -d "$1" ] ; then + emucon_print_error "Specified directory does not exist: $1" + emucon_abort -v + fi +} + +emucon_ensure_file_exists() +{ + if [ ! -f "$1" ] ; then + emucon_print_error "Specified file does not exist: $1" + emucon_abort -v + fi +} + +emucon_ensure_is_installed() +{ + local cmd + cmd="$1" + + if ! which "${cmd}" > /dev/null ; then + emucon_print_error "${cmd} was not found in PATH!" + emucon_print_error 'Please install it first.' + emucon_abort -v + fi +} + diff --git a/emucon-tools/runtime/lib/emucon/qcow-create.sh b/emucon-tools/runtime/lib/emucon/qcow-create.sh new file mode 100644 index 0000000..636f60f --- /dev/null +++ b/emucon-tools/runtime/lib/emucon/qcow-create.sh @@ -0,0 +1,87 @@ +# +# Implementation: emucon-qcow create +# + +__cmd_name="${__cmd_name} create" + + +# ========== Helper Functions ========== + +__print_usage() { + cat <<- EOT + USAGE: + ${__cmd_name} -o + + DESCRIPTION: + Creates a qcow-image. + + OPTIONS: + -o, --options + List of image options. + + ARGUMENTS: + + Path of the image to create. + + EOT +} + +__has_option() { + local name="$1" + local list="$2" + echo "${list}" | grep "${name}=" > /dev/null +} + + +# ========== Script's Begin ========== + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse subcommand's command line arguments +longopts='options:,help' +cmdargs=$(emucon_parse_cmdargs -s 'o:,h' -l "${longopts}" -- "$@") || emucon_abort + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while [ $# -gt 0 ] ; do + case "$1" in + -o|--options) + options="$2" + shift 1 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort ;; + esac + shift 1 +done + +outpath="$1" + +emucon_check_required_arg '' "${outpath}" +emucon_ensure_is_installed 'qemu-img' + +# Option 'size' is required, when option 'backing_file' is not used! +if ! __has_option 'size' "${options}" && ! __has_option 'backing_file' "${options}" ; then + emucon_print_error "Size of the qcow-image is missing!" + emucon_print_error "It must be specified in the options list, like:" + if [ -n "${options}" ] ; then + options="${options},size=2G" + else + options="size=2G" + fi + emucon_print_error "${__cmd_name} -o ${options} ${outpath}" + emucon_abort -v +fi + +emucon_print_info "Creating qcow-image..." +qemu-img create -f qcow2 -o "${options}" "${outpath}" + diff --git a/emucon-tools/runtime/lib/emucon/qcow-mkfs.sh b/emucon-tools/runtime/lib/emucon/qcow-mkfs.sh new file mode 100644 index 0000000..00a739f --- /dev/null +++ b/emucon-tools/runtime/lib/emucon/qcow-mkfs.sh @@ -0,0 +1,88 @@ +# +# Implementation: emucon-qcow mkfs +# + +__cmd_name="${__cmd_name} mkfs" + + +# ========== Helper Functions ========== + +__print_usage() { + cat <<- EOT + USAGE: + ${__cmd_name} --fs-type + + DESCRIPTION: + Creates a new filesystem on specified qcow-image. + + OPTIONS: + -t, --fs-type + Filesystem (ext4, xfs, etc.) to create. + + ARGUMENTS: + + Path of the image to create filesystem on. + + EOT +} + + +# ========== Script's Begin ========== + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse subcommand's command line arguments +cmdargs=$(emucon_parse_cmdargs -s 't:h' -l 'fs-type:,help' -- "$@") || emucon_abort + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while [ $# -gt 0 ] ; do + case "$1" in + -t|--fs-type) + fstype="$2" + shift 1 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort ;; + esac + shift 1 +done + +imgpath="$1" + +emucon_check_required_arg '-t/--fs-type' "${fstype}" +emucon_check_required_arg '' "${imgpath}" +emucon_ensure_is_installed "mkfs.${fstype}" + +emucon_print_info "Creating mountpoint for qcow-image..." +mntpath=$(mktemp -d --tmpdir 'emucon-XXXXX') + +__cleanup() { + emucon_print_info 'Cleaning up...' + emucon-qcow unmount "${mntpath}" + rm -r -v "${mntpath}" +} + +trap __cleanup EXIT + +emucon-qcow mount "${imgpath}" "${mntpath}" || emucon_abort +rawimgpath=$(__to_mounted_image_path "${imgpath}" "${mntpath}") + +emucon_print_info "Creating filesystem..." +case "${fstype}" in + ext4) + mkfs.ext4 -F "${rawimgpath}" || emucon_abort -v ;; + *) + emucon_print_error "Unsupported filesystem: ${fstype}" + emucon_abort -v ;; +esac + diff --git a/emucon-tools/runtime/lib/emucon/qcow-mount.sh b/emucon-tools/runtime/lib/emucon/qcow-mount.sh new file mode 100644 index 0000000..663f437 --- /dev/null +++ b/emucon-tools/runtime/lib/emucon/qcow-mount.sh @@ -0,0 +1,133 @@ +# +# Implementation: emucon-qcow mount +# + +__cmd_name="${__cmd_name} mount" + + +# ========== Helper Functions ========== + +__print_usage() { + cat <<- EOT + USAGE: + ${__cmd_name} [--fs-type --fs-path ] + + DESCRIPTION: + Mounts a qcow-image at the specified mount-point. + + OPTIONS: + --fs-type + Filesystem (ext4, xfs, etc.) to mount at , if the qcow-image contains one. + + --fs-path + Path to mount the qcow-image's filesystem at. + + ARGUMENTS: + + Path of the image to mount. + + + Mount-point for the image. + + EOT +} + + +# ========== Script's Begin ========== + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse subcommand's command line arguments +longopts='fs-type:,fs-path:,help' +cmdargs=$(emucon_parse_cmdargs -s 'h' -l "${longopts}" -- "$@") || emucon_abort + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while [ $# -gt 0 ] ; do + case "$1" in + --fs-type) + fstype="$2" + shift 1 ;; + --fs-path) + fspath="$2" + shift 1 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort ;; + esac + shift 1 +done + +imgpath="$1" +mntpath="$2" + +emucon_check_required_arg '' "${imgpath}" +emucon_check_required_arg '' "${mntpath}" + +emucon_print_info "Mounting qcow-image at ${mntpath}..." +case "${mntpath}" in + /dev/nbd*) + # NBD mode + emucon_ensure_is_installed 'qemu-nbd' + + sudo qemu-nbd --discard unmap \ + --cache writeback \ + --connect "${mntpath}" \ + "${imgpath}" || emucon_abort -v + ;; + *) + # xmount mode + emucon_ensure_is_installed 'xmount' + emucon_ensure_is_installed 'fusermount' + + xmount --out raw \ + --in qemu "${imgpath}" \ + --inopts 'qemuwritable=true,bdrv_cache=writeback' \ + --cache writethrough \ + "${mntpath}" || emucon_abort -v + ;; +esac + +__cleanup() { + if [ $? -eq 0 ] ; then + # Normal termination + exit 0 + fi + + emucon_print_info "Un-mounting qcow-image at ${mntpath}..." + case "${mntpath}" in + /dev/nbd*) + sudo qemu-nbd --disconnect "${mntpath}" ;; + *) + fusermount -u -z "${mntpath}" ;; + esac +} + +trap __cleanup EXIT + +if [ -n "${fstype}" ] ; then + emucon_check_required_arg '--fs-path' "${fspath}" + + emucon_print_info "Mounting qcow-image's filesystem at ${fspath}..." + case "${mntpath}" in + /dev/nbd*) + # System-mount mode + sudo mount -t "${fstype}" "${mntpath}" "${fspath}" + sudo chmod a+rwx "${fspath}" ;; + *) + # FUSE-mount mode + emucon_ensure_is_installed 'lklfuse' + rawimgpath=$(__to_mounted_image_path "${imgpath}" "${mntpath}") + lklfuse "${rawimgpath}" "${fspath}" -o "allow_other,use_ino,rw,type=${fstype}" ;; + esac +fi + diff --git a/emucon-tools/runtime/lib/emucon/qcow-unmount.sh b/emucon-tools/runtime/lib/emucon/qcow-unmount.sh new file mode 100644 index 0000000..1151605 --- /dev/null +++ b/emucon-tools/runtime/lib/emucon/qcow-unmount.sh @@ -0,0 +1,103 @@ +# +# Implementation: emucon-qcow unmount +# + +__cmd_name="${__cmd_name} unmount" + + +# ========== Helper Functions ========== + +__print_usage() { + cat <<- EOT + USAGE: + ${__cmd_name} [--fs-path ] + + DESCRIPTION: + Un-mounts a qcow-image from the specified mount-point. + + OPTIONS: + --fs-path + Path to unmount the qcow-image's filesystem from. + + --no-sync + Do not execute a sync after unmount. + + ARGUMENTS: + + Mount-point for the image. + + EOT +} + +__run_sync() { + test "${dosync}" = 'y' && sync +} + + +# ========== Script's Begin ========== + +if [ $# -eq 0 ] ; then + __print_usage + emucon_exit +fi + +# Parse subcommand's command line arguments +longopts='fs-path:,no-sync,help' +cmdargs=$(emucon_parse_cmdargs -s 'h' -l "${longopts}" -- "$@") || emucon_abort + +dosync='y' + +# Lookup parsed parameters and their arguments +eval set -- ${cmdargs} +while [ $# -gt 0 ] ; do + case "$1" in + --fs-path) + fspath="$2" + shift 1 ;; + --no-sync) + dosync='n' + shift 1 ;; + -h|--help) + __print_usage + emucon_exit ;; + --) + shift 1 + break ;; + *) + emucon_print_invalid_cmdargs_error "${cmdargs}" + emucon_abort ;; + esac + shift 1 +done + +mntpath="$1" + +emucon_check_required_arg '' "${mntpath}" + +if [ -n "${fspath}" ] ; then + __run_sync + emucon_print_info "Un-mounting qcow-image's filesystem from ${fspath}..." + emucon_ensure_dir_exists "${fspath}" + case "${mntpath}" in + /dev/nbd*) + sudo umount "${fspath}" ;; + *) + emucon_ensure_is_installed 'fusermount' + fusermount -u -z "${fspath}" ;; + esac +fi + +__run_sync + +emucon_print_info "Un-mounting qcow-image from ${mntpath}..." +case "${mntpath}" in + /dev/nbd*) + emucon_ensure_is_installed 'qemu-nbd' + sudo qemu-nbd --disconnect "${mntpath}" ;; + *) + emucon_ensure_is_installed 'fusermount' + fusermount -u -z "${mntpath}" ;; +esac + +__run_sync + diff --git a/emucon-tools/runtime/share/emucon/sudoers.template b/emucon-tools/runtime/share/emucon/sudoers.template new file mode 100644 index 0000000..dba3a89 --- /dev/null +++ b/emucon-tools/runtime/share/emucon/sudoers.template @@ -0,0 +1,48 @@ +# +# Privileged commands for emucon-tools, +# required to be run without password +# + +# Allowed mount/umount calls +Cmnd_Alias MOUNT_COMMANDS = /bin/mount -t overlay *, \ + /bin/umount + + +# Allowed rm command calls +Cmnd_Alias RM_COMMANDS = /bin/rm -[rv] -[rv] /tmp/eaas-*, \ + /bin/rm -[rv] /tmp/eaas-*, \ + /bin/rm /tmp/eaas-* + + +# Allowed chown command calls +Cmnd_Alias CHOWN_COMMANDS = /bin/chown --recursive {{user}}\:{{user}} /tmp/eaas-*, \ + /bin/chown -R {{user}}\:{{user}} /tmp/eaas-*, \ + /bin/chown --recursive {{user}}\:{{user}} state, \ + /bin/chown -R {{user}}\:{{user}} state + + +# Allowed runc command calls +Cmnd_Alias RUNC_COMMANDS = {{install-dir}}/bin/runc --* run *, \ + {{install-dir}}/bin/runc run *, \ + {{install-dir}}/bin/runc --* pause *, \ + {{install-dir}}/bin/runc pause *, \ + {{install-dir}}/bin/runc --* resume *, \ + {{install-dir}}/bin/runc resume *, \ + {{install-dir}}/bin/runc --* checkpoint *, \ + {{install-dir}}/bin/runc checkpoint *, \ + {{install-dir}}/bin/runc --* restore *, \ + {{install-dir}}/bin/runc restore *, \ + {{install-dir}}/bin/runc --* exec *, \ + {{install-dir}}/bin/runc exec *, \ + {{install-dir}}/bin/runc --* list *, \ + {{install-dir}}/bin/runc list *, \ + {{install-dir}}/bin/runc --* list, \ + {{install-dir}}/bin/runc list, \ + {{install-dir}}/bin/runc --* kill *, \ + {{install-dir}}/bin/runc kill *, \ + {{install-dir}}/bin/runc --* ps *, \ + {{install-dir}}/bin/runc ps * + + +{{user}} ALL = NOPASSWD: MOUNT_COMMANDS, RUNC_COMMANDS, RM_COMMANDS, CHOWN_COMMANDS + From 59ea4e284a0d310cb1f64697910cdd02fd5eff27 Mon Sep 17 00:00:00 2001 From: peeqle Date: Mon, 23 Jun 2025 11:06:04 +0300 Subject: [PATCH 4/5] emucomp-tools removal from internal files --- emucon-tools/README.md | 45 ---- emucon-tools/bootstrap.sh | 20 -- .../commands/layer/layers/base/Dockerfile | 10 - .../layer/layers/base/data/bwfla.list | 1 - .../layer/layers/base/data/pin-bwfla.pref | 4 - .../commands/layer/layers/base/data/xpra.list | 1 - .../builder/commands/layer/layers/base/run.sh | 140 ------------- .../layers/base/scripts/dependencies.txt | 13 -- .../layer/layers/base/scripts/emucon-init | 140 ------------- .../layer/layers/base/scripts/emulators.txt | 21 -- .../layer/layers/base/scripts/install.sh | 91 -------- .../commands/layer/layers/emulator/run.sh | 194 ------------------ .../layer/layers/emulator/scripts/install.sh | 40 ---- emucon-tools/builder/commands/layer/run.sh | 57 ----- emucon-tools/builder/commands/tool/run.sh | 100 --------- .../tool/scripts/build-image-tools.sh | 21 -- .../commands/tool/scripts/build-runc.sh | 18 -- .../tool/scripts/build-runtime-tools.sh | 21 -- .../builder/commands/tool/scripts/build.sh | 32 --- .../commands/tool/scripts/prepare-go.sh | 19 -- emucon-tools/builder/emucon-build | 57 ----- emucon-tools/install.sh | 103 ---------- emucon-tools/installer/install-deps.sh | 13 -- emucon-tools/installer/install-oci-tools.sh | 96 --------- emucon-tools/installer/install-scripts.sh | 129 ------------ emucon-tools/runtime/lib/emucon/helpers.sh | 175 ---------------- .../runtime/lib/emucon/qcow-create.sh | 87 -------- emucon-tools/runtime/lib/emucon/qcow-mkfs.sh | 88 -------- emucon-tools/runtime/lib/emucon/qcow-mount.sh | 133 ------------ .../runtime/lib/emucon/qcow-unmount.sh | 103 ---------- .../runtime/share/emucon/sudoers.template | 48 ----- 31 files changed, 2020 deletions(-) delete mode 100644 emucon-tools/README.md delete mode 100755 emucon-tools/bootstrap.sh delete mode 100644 emucon-tools/builder/commands/layer/layers/base/Dockerfile delete mode 100644 emucon-tools/builder/commands/layer/layers/base/data/bwfla.list delete mode 100644 emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref delete mode 100644 emucon-tools/builder/commands/layer/layers/base/data/xpra.list delete mode 100755 emucon-tools/builder/commands/layer/layers/base/run.sh delete mode 100644 emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt delete mode 100644 emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init delete mode 100644 emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt delete mode 100755 emucon-tools/builder/commands/layer/layers/base/scripts/install.sh delete mode 100755 emucon-tools/builder/commands/layer/layers/emulator/run.sh delete mode 100755 emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh delete mode 100755 emucon-tools/builder/commands/layer/run.sh delete mode 100755 emucon-tools/builder/commands/tool/run.sh delete mode 100644 emucon-tools/builder/commands/tool/scripts/build-image-tools.sh delete mode 100644 emucon-tools/builder/commands/tool/scripts/build-runc.sh delete mode 100644 emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh delete mode 100755 emucon-tools/builder/commands/tool/scripts/build.sh delete mode 100644 emucon-tools/builder/commands/tool/scripts/prepare-go.sh delete mode 100755 emucon-tools/builder/emucon-build delete mode 100755 emucon-tools/install.sh delete mode 100755 emucon-tools/installer/install-deps.sh delete mode 100755 emucon-tools/installer/install-oci-tools.sh delete mode 100755 emucon-tools/installer/install-scripts.sh delete mode 100644 emucon-tools/runtime/lib/emucon/helpers.sh delete mode 100644 emucon-tools/runtime/lib/emucon/qcow-create.sh delete mode 100644 emucon-tools/runtime/lib/emucon/qcow-mkfs.sh delete mode 100644 emucon-tools/runtime/lib/emucon/qcow-mount.sh delete mode 100644 emucon-tools/runtime/lib/emucon/qcow-unmount.sh delete mode 100644 emucon-tools/runtime/share/emucon/sudoers.template diff --git a/emucon-tools/README.md b/emucon-tools/README.md deleted file mode 100644 index d9f5961..0000000 --- a/emucon-tools/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Introduction - -`emucon-tools` is a collection of scripts for running containerized emulators. - - -# Bootstrapping - -The scripts and tools assume that `runtime/bin` folder is in your `PATH`. -To bootstrap a system for a single terminal session, simply run in your shell: -``` -$ . ./bootstrap.sh -``` - - -# Installation - -To install `emucon-tools` and all dependencies into `/usr/local`, simply run: -``` -$ ./install.sh --destination /usr/local -``` - -Some dependencies will be built from source and some installed using host's package manager. - -The previous command will also add a sudoers-configuration for the current user, to be able -to run required privileged commands without asking user password. A different user can be -specified with `--user ` option. For all available options, please run: -``` -$ ./install.sh --help -``` - -# Installation in Docker-Container - -The OCI tools must be built on the host (outside of Docker) by running: -``` -$ mkdir /tmp/oci-tools -$ ./installer/install-oci-tools.sh --destination /tmp/oci-tools -``` - -Then in your Dockerfile add: -``` -COPY /tmp/oci-tools /usr/local -RUN /installer/install-scripts.sh --destination /usr/local \ - && /installer/install-deps.sh -``` - diff --git a/emucon-tools/bootstrap.sh b/emucon-tools/bootstrap.sh deleted file mode 100755 index 0f636ac..0000000 --- a/emucon-tools/bootstrap.sh +++ /dev/null @@ -1,20 +0,0 @@ -# -# Bootstrapping for emucon-tools -# - -curdir="$(pwd)" -bindir="${curdir}/runtime/bin" - -if [ ! -f "${bindir}/emucon-init.sh" ] ; then - echo 'Unexpected directory layout!' - echo 'Aborting...' -else - echo 'Modifying PATH for current terminal session...' - echo "Adding ${bindir}" - - export PATH="${bindir}:${PATH}" -fi - -unset bindir -unset curdir - diff --git a/emucon-tools/builder/commands/layer/layers/base/Dockerfile b/emucon-tools/builder/commands/layer/layers/base/Dockerfile deleted file mode 100644 index 76e0940..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM phusion/baseimage:0.9.22 - -COPY scripts /emucon-scripts/ - -# Set up custom repo -COPY data/*.list /etc/apt/sources.list.d/ -COPY data/pin-bwfla.pref /etc/apt/preferences.d/pin-bwfla.pref - -RUN /sbin/my_init -- /emucon-scripts/install.sh - diff --git a/emucon-tools/builder/commands/layer/layers/base/data/bwfla.list b/emucon-tools/builder/commands/layer/layers/base/data/bwfla.list deleted file mode 100644 index fec30e6..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/data/bwfla.list +++ /dev/null @@ -1 +0,0 @@ -deb http://packages.eaas.uni-freiburg.de/ xenial bwfla diff --git a/emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref b/emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref deleted file mode 100644 index cb9e1ad..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/data/pin-bwfla.pref +++ /dev/null @@ -1,4 +0,0 @@ -Package: * -Pin: release c=bwfla -Pin-Priority: 1001 - diff --git a/emucon-tools/builder/commands/layer/layers/base/data/xpra.list b/emucon-tools/builder/commands/layer/layers/base/data/xpra.list deleted file mode 100644 index 5a28ffd..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/data/xpra.list +++ /dev/null @@ -1 +0,0 @@ -deb https://xpra.org/ xenial main diff --git a/emucon-tools/builder/commands/layer/layers/base/run.sh b/emucon-tools/builder/commands/layer/layers/base/run.sh deleted file mode 100755 index 8cb34ac..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/run.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/sh - -__cmd_name="${__cmd_name} base" - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} -t [-s ] -o - - DESCRIPTION: - Builds container's base layer. - - OPTIONS: - -t, --output-type - Output type for built layer: - tree: Filesystem tree - qcow: QCow container image - - -o, --output-path - Output path for built layer - - -s, --output-size - Output image size in bytes (with optional suffix K, M or G) - - -n, --use-nbd - Connect qcow-image to specified NBD device - - EOT -} - - -# ========== Script's Begin ========== - -. $(emucon-paths helpers.sh) - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse script's command line arguments -shortopts='t:o:s:n:h' -longopts='output-type:,output-path:,output-size:,use-nbd:,help' -cmdargs=$(emucon_parse_cmdargs -s "${shortopts}" -l "${longopts}" -- "$@") -if emucon_cmd_failed ; then - emucon_abort -fi - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while true ; do - case "$1" in - -t|--output-type) - outtype="$2" - shift 2 ;; - -o|--output-path) - outpath="$2" - shift 2 ;; - -s|--output-size) - outsize="$2" - shift 2 ;; - -n|--use-nbd) - nbdpath="$2" - shift 2 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v ;; - esac -done - -# Check arguments -emucon_check_required_arg "-t/--output-type" "${outtype}" -emucon_check_required_arg "-o/--output-path" "${outpath}" - -# Prepare output image/directory -case "${outtype}" in - tree) - emucon_ensure_dir_exists "${outpath}" - outdir="${outpath}" - ;; - qcow) - emucon_check_required_arg "-s/--output-size" "${outsize}" - emucon-qcow create -o "size=${outsize}" "${outpath}" || emucon_abort - emucon-qcow mkfs --fs-type ext4 "${outpath}" || emucon_abort - - emucon_print_info "Creating temporary mountpoints for qcow-image..." - tmpdir=$(mktemp -d --tmpdir 'emucon-XXXXX') - outdir="${tmpdir}/fs" - mkdir -v -p "${outdir}" - if [ -n "${nbdpath}" ] ; then - rawdir="${nbdpath}" - else - rawdir="${tmpdir}/raw" - mkdir -v -p "${rawdir}" - fi - - __cleanup() { - emucon_print_info 'Cleaning up...' - emucon-qcow unmount --fs-path "${outdir}" "${rawdir}" || emucon_abort - rm -r -v "${tmpdir}" - } - - trap __cleanup EXIT - - emucon-qcow mount --fs-type ext4 --fs-path "${outdir}" "${outpath}" "${rawdir}" || emucon_abort - ;; - *) - emucon_print_error "Invalid output type specified: ${outtype}" - emucon_abort -v - ;; -esac - -# Docker names -tag='eaas/emucon-base-layer:latest' -name='eaas-emucon-base' - -emucon_print_info "Building new image for '${tag}'..." -sudo docker build --no-cache --force-rm=true -t "${tag}" . || emucon_abort -v - -emucon_print_info "Creating container from image '${tag}'..." -container=$(sudo docker create --name "${name}" "${tag}") || emucon_abort -v - -emucon_print_info "Exporting image to ${outdir}" -excludes='--exclude=dev/* --exclude=proc/* --exclude=usr/share/doc/* --exclude=usr/share/man/* --exclude=*/__pycache__/*' -tarargs="--ignore-failed-read --totals -C ${outdir}" -sudo docker export "${container}" | tar ${excludes} ${tarargs} -xvf - - -emucon_print_info "Removing container ${name}" -sudo docker rm "${name}" - diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt b/emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt deleted file mode 100644 index 9e78691..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/scripts/dependencies.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Common dependencies for supported emulators -# (for Debian-based distributions) -# -# NOTE: empty lines and lines starting -# with # will be skipped! - -libguac-client-sdlonp0 -libsdl1.2debian -qemu-utils -vde2 -xpra -python-pip - diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init b/emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init deleted file mode 100644 index c6f1cce..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/scripts/emucon-init +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/sh - -# -# Script for starting following processes: -# - VDE-Switches (optional) -# - XPRA-Server (optional) -# - Emulator -# - -readonly display=':7000' -export HOME='/home/bwfla' - - -# ========== Helper Functions ========== - -__abort() { - echo 'Aborting...' - exit 1 -} - -__list_nics() { - ls "$1" | grep 'nic' -} - -__wait_until_ready() { - local curiter='-1' - local delay='1s' - local service="$1" - local path="$2" - local maxiter="$3" - while [ ! -e "${path}" ] ; do - curiter=$((curiter + 1)) - if [ "${curiter}" -gt "${maxiter}" ] ; then - echo "==> FAILED: ${service}" - __abort - fi - sleep "${delay}" - done - echo "==> READY: ${service}" -} - -__on_exit() { - echo 'Waiting for main process to terminate...' - wait "${mpid}" - - echo 'Terminating background processes...' - kill -TERM -1 - - echo 'Waiting for background processes to terminate...' - while ! wait ; do - : ; - done - - if [ -n "${xprasock}" ] ; then - echo '===== XPRA LOG =========================' - cat "/run/user/$(id --user)/xpra/${display}.log" - fi -} - - -# ========== Script's Begin ========== - -# Parse command line arguments -cmdargs=$(getopt -o 'n:s:' -l 'networks-dir:,xpra-socket:' -n 'emucon-init' -- "$@") -if [ $? -ne 0 ] ; then - echo 'Parsing command arguments failed!' - __abort -fi - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while [ $# -gt 0 ] ; do - case "$1" in - -n|--networks-dir) - nicsdir="$2" - shift 1 ;; - -s|--xpra-socket) - xprasock="$2" - shift 1 ;; - --) - shift 1 - break ;; - *) - echo "Invalid command line arguments found: ${cmdargs}" - __abort ;; - esac - shift 1 -done - -if [ -z "${nicsdir}" ] || [ ! -d "${nicsdir}" ] ; then - echo "Required argument -n/--networks-dir is missing!" - __abort -fi - -trap __on_exit EXIT - -if [ -n "${xprasock}" ] ; then - echo 'Starting XPRA-daemon...' - xpra start ${display:?} \ - --start="sh -c 'xhost +si:localuser:bwfla; touch /tmp/xpra-started'" \ - --bind="${xprasock:?}" \ - --daemon=yes \ - --html=off - - echo '==> DONE: XPRA-daemon started' - cmdargs="--xpra-socket ${xprasock}" -fi - -# NOTE: We need to start a vde-hub for every NIC. -echo 'Starting VDE-hub processes...' -for nic in $(__list_nics "${nicsdir}") ; do - nicpath="${nicsdir}/${nic}" - vde_switch -hub -s "${nicpath}" -d - echo "==> DONE: ${nicpath}" -done - -echo 'Waiting for all VDE-hubs to be ready...' -for nic in $(__list_nics "${nicsdir}") ; do - __wait_until_ready "/etc/service/${nic}" "${nicsdir}/${nic}/ctl" '5' -done - -if [ -n "${xprasock}" ] ; then - export DISPLAY="${display:?}" - echo 'Waiting for XPRA-daemon to be ready...' - __wait_until_ready 'XPRA-daemon' '/tmp/xpra-started' '30' -fi - -__on_terminate() { - echo 'Terminating main process...' - kill -TERM "${mpid}" -} - -trap __on_terminate TERM - -echo 'Running main command...' -"$@" & - -readonly mpid="$!" -wait "${mpid}" - diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt b/emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt deleted file mode 100644 index 32b7bbd..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/scripts/emulators.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Package names for supported emulators -# (for Debian-based distributions) -# -# NOTE: empty lines and lines starting -# with # will be skipped! - -fs-uae -hatari -sheepshaver -beebem -kegs-sdl -vice-sdl -basilisk2 -pce-atari-st -pce-dos -pce-ibmpc -pce-macplus -dosbox -qemu-system-x86 -qemu-system-ppc - diff --git a/emucon-tools/builder/commands/layer/layers/base/scripts/install.sh b/emucon-tools/builder/commands/layer/layers/base/scripts/install.sh deleted file mode 100755 index 24520c9..0000000 --- a/emucon-tools/builder/commands/layer/layers/base/scripts/install.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/sh - -# Directory containing scripts -readonly scripts=$(dirname $(readlink -f $0)) - -# ========== Internal Helpers ========== - -__print_message() -{ - echo "[container] $1" -} - -__to_flat_list() -{ - # Skip all commented out and empty lines - cat - | grep --invert-match -E '#|^$' | tr '\n' ' ' -} - -__print_emulators() -{ - cat "${scripts}/emulators.txt" | __to_flat_list -} - -__print_dependencies() -{ - cat "${scripts}/dependencies.txt" | __to_flat_list -} - - -# ========== Script's Begin ========== - -__print_message "Executing script $0..." - -export DEBIAN_FRONTEND=noninteractive - -__print_message 'Upgrading system packages...' -apt-get update -apt-get upgrade -y -apt-get autoremove - -__print_message 'Installing emulators and deps...' -readonly emulators="$(__print_emulators)" -readonly deps="$(__print_dependencies)" -apt-get install -y --allow-unauthenticated ${deps} ${emulators} -apt-get autoremove - -__print_message 'Installing xpra deps...' -pip install python-uinput - -__print_message 'Removing emulators...' -apt-get remove -y --allow-unauthenticated ${emulators} - -__print_message 'Installing emucon-init...' -install -v -m 'a=rx' "${scripts}/emucon-init" '/usr/bin/emucon-init' - -# NOTE: apt-get tool tries to drop privileges, when run as root. -# For this a user _apt is created inside of the container. -# But when we export and run containers inside a user-namespace, -# apt-get tool fails to change the owner of some temporary folders -# with "Permission denied" errors. This happens because the host -# user normally doesn't have the permissions to change owners of -# folders without elevated privileges. - -# HACK: change apt-tool's user ID to root -usermod --non-unique --uid 0 _apt - -# Add main user -readonly uid='1000' -addgroup --gid "${uid}" bwfla -useradd -ms /bin/bash --uid "${uid}" --gid bwfla bwfla -adduser bwfla xpra - -__print_message 'Update Xpra config...' -sed -i '$s/$/ -nolisten local/' /etc/xpra/conf.d/55_server_x11.conf -sed -i '$s/-auth *[^ ]*//' /etc/xpra/conf.d/55_server_x11.conf - -# Xpra runtime directory -mkdir -p "/run/user/${uid}/xpra" -chmod a=rwx "/run/user/${uid}/xpra" - -__print_message 'Cleaning up package manager...' -apt-get clean - -__print_message 'Cleaning up directories...' -rm -rf /var/lib/apt/lists/* -rm -rf /var/tmp/* -rm -rf /tmp/* -rm -rf "${scripts}" - -__print_message "Script $0 finished" - diff --git a/emucon-tools/builder/commands/layer/layers/emulator/run.sh b/emucon-tools/builder/commands/layer/layers/emulator/run.sh deleted file mode 100755 index 8911bb7..0000000 --- a/emucon-tools/builder/commands/layer/layers/emulator/run.sh +++ /dev/null @@ -1,194 +0,0 @@ -#!/bin/sh - -__cmd_name="${__cmd_name} emulator" - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} -t -o -b [...] - - DESCRIPTION: - Builds container's emulator layer. - - OPTIONS: - -t, --output-type - Output type for built layer: - tree: Filesystem tree - qcow: QCow container image - - -o, --output-dir - Output directory for built files. - - -b, --base-layer - Base layer directory or qcow-image. - - -n, --use-nbd - Connect qcow-image to specified NBD device. - - --list - List all supported emulator package names. - - ARGUMENTS: - - The emulator's package name. - - EOT -} - -# All supported emulators -__print_emulator_names() -{ - # Skip all commented out and empty lines - cat "$(emucon_get_current_dir)/../base/scripts/emulators.txt" \ - | grep --invert-match -E '#|^$' -} - -__check_emulator_name() -{ - # Find first match and stop immediately - __print_emulator_names | grep -m 1 --line-regexp "$1" > /dev/null -} - - -# ========== Script's Begin ========== - -. $(emucon-paths helpers.sh) - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse script's command line arguments -shortopts='t:o:b:n:h' -longopts='output-type:,output-dir:,base-layer:,use-nbd:,list,help' -cmdargs=$(emucon_parse_cmdargs -s "${shortopts}" -l "${longopts}" -- "$@") || emucon_abort - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while true ; do - case "$1" in - -b|--base-layer) - basepath="$2" - shift 2 ;; - -t|--output-type) - outtype="$2" - shift 2 ;; - -o|--output-dir) - outdir="$2" - shift 2 ;; - -n|--use-nbd) - nbdpath="$2" - shift 2 ;; - --list) - __print_emulator_names - emucon_exit ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v ;; - esac -done - -# Check arguments -emucon_check_required_arg "-t/--output-type" "${outtype}" -emucon_check_required_arg "-o/--output-dir" "${outdir}" -emucon_check_required_arg "-b/--base-layer" "${basepath}" -emucon_ensure_dir_exists "${outdir}" -case "${outtype}" in - tree) - emucon_ensure_dir_exists "${basepath}" - ;; - qcow) - emucon_ensure_file_exists "${basepath}" - ;; - *) - emucon_print_error "Invalid output type specified: ${outtype}" - emucon_abort -v - ;; -esac - -emulators='' -if [ $# -gt 0 ] ; then - # Check specified emulator names - for emulator in "$@" ; do - if ! __check_emulator_name "${emulator}" ; then - emucon_print_error "Invalid emulator's package name specified: ${emulator}" - emucon_abort -v - fi - emulators="${emulators} ${emulator}" - done -else - # No emulators specified, use all - emucon_print_warning 'No emulators specified! Build all supported.' - emulators="$(__print_emulator_names | tr '\n' ' ')" -fi - -# User for user-namespace mapping -user=$(id --user --name) -group=$(id --group --name) - -# Path for scripts to be run in containers -scripts="$(emucon_get_current_dir)/scripts" - -emucon_print_info 'Creating temporary directory...' -tmpdir=$(mktemp -d --tmpdir 'emucon-XXXXX') - -# Generate requested layers... -for emulator in ${emulators} ; do - emucon_print_info "Starting emulator-layer generation for ${emulator}..." - workdir="${tmpdir}/${emulator}" - mkdir -p "${workdir}" - case "${outtype}" in - tree) - emuoutpath="${outdir}/${emulator}" - mkdir -p "${emuoutpath}" - ;; - qcow) - emuoutpath="${outdir}/${emulator}.qcow" - emucon-qcow create -o "backing_file=${basepath}" "${emuoutpath}" - ;; - esac - - emucon_print_info "Using working directory: ${workdir}" - - emucon_print_info "Generating config.json for ${emulator}..." - emucon-cgen --mount "${scripts}:/emucon-scripts:bind:ro" \ - -- /sbin/my_init -- /emucon-scripts/install.sh "${emulator}" \ - > "${workdir}/config.json" - - emucon_print_info "Running container for ${emulator}..." - case "${outtype}" in - tree) - options="-t overlay -l ${basepath} -u ${emuoutpath}" ;; - qcow) - options="-t qcow -i ${emuoutpath}" - if [ -n "${nbdpath}" ] ; then - options="${options} --use-nbd ${nbdpath}" - fi - ;; - esac - - conid=$(mktemp --dry-run "congen-XXXXX") - if emucon-run -w "${workdir}" ${options} -c "${conid}" ; then - emucon_print_info "Emulator-layer for ${emulator} created at: ${emuoutpath}" - else - emucon_print_error "Generating emulator-layer for ${emulator} failed!" - fi - - emucon_print_info 'Cleaning up...' - rm -v -r "${workdir}" -done - -emucon_print_info 'Final cleanup...' -rm -v -r "${tmpdir}" - diff --git a/emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh b/emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh deleted file mode 100755 index 483d955..0000000 --- a/emucon-tools/builder/commands/layer/layers/emulator/scripts/install.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh - -# Directory containing scripts -readonly scripts=$(dirname $(readlink -f $0)) - - -# ========== Internal Helpers ========== - -__print_message() -{ - echo "[container] $1" -} - - -# ========== Script's Begin ========== - -__print_message "Executing script $0..." - -if [ $# -eq 0 ] ; then - __print_message 'No packages specified!' - __print_message "Usage: $0 [...]" - exit 1 -fi - -export DEBIAN_FRONTEND=noninteractive - -__print_message 'Installing packages...' -apt-get update -apt-get install -y --allow-unauthenticated "$@" - -__print_message 'Cleaning up package manager...' -apt-get clean - -__print_message 'Cleaning up directories...' -rm -rf /var/lib/apt/lists/* -rm -rf /var/tmp/* -rm -rf /tmp/* - -__print_message "Script $0 finished" - diff --git a/emucon-tools/builder/commands/layer/run.sh b/emucon-tools/builder/commands/layer/run.sh deleted file mode 100755 index 2f9a706..0000000 --- a/emucon-tools/builder/commands/layer/run.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh - -export __cmd_name="${__cmd_name} layer" - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} [...] - - DESCRIPTION: - Builds the specified container layer. - - ARGUMENTS: - - The name of the layer to build: - base - Base layer - emulator - Emulator layers - - EOT -} - - -# ========== Script's Begin ========== - -. $(emucon-paths helpers.sh) - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse script's command line arguments -case "$1" in - -h|--help) - __print_usage - emucon_exit ;; - base|emulator) - layer="$1" - shift 1 ;; - -*) - emucon_print_error "${__cmd_name}: unrecognized option '$1'" - emucon_print_cmdargs_parsing_error - emucon_abort -v ;; - *) - emucon_print_error "${__cmd_name}: invalid name -- '$1'" - emucon_print_cmdargs_parsing_error - emucon_abort -v ;; -esac - -# Running subcommand -cd "layers/${layer}" -./run.sh "$@" - diff --git a/emucon-tools/builder/commands/tool/run.sh b/emucon-tools/builder/commands/tool/run.sh deleted file mode 100755 index 810d016..0000000 --- a/emucon-tools/builder/commands/tool/run.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/sh - -__cmd_name="${__cmd_name} tool" - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} -o [...] - - DESCRIPTION: - Builds the specified OCI tool(s) from sources. - - ARGUMENTS: - - The name of the tool to build: - image-tools - OCI image-tools - runtime-tools - OCI runtime-tools - runc - OCI runc-tool - - OPTIONS: - -o, --output-dir - Output directory for built files. - - EOT -} - - -# ========== Script's Begin ========== - -. emucon-init.sh - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse script's command line arguments -shortopts='o:h' -longopts='output-dir:,help' -cmdargs=$(emucon_parse_cmdargs -s "${shortopts}" -l "${longopts}" -- "$@") -if emucon_cmd_failed ; then - emucon_abort -fi - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while true ; do - case "$1" in - -o|--output-dir) - outdir="$2" - shift 2 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v ;; - esac -done - -# Tools to build -tools="$@" -if [ -z "${tools}" ] ; then - tools='image-tools runtime-tools runc' - emucon_print_warning "No tools specified! Building all: ${tools}" -else - # Check tool names - for tool in ${tools} ; do - case "${tool}" in - image-tools|runtime-tools|runc) - # Valid name - ;; - *) - emucon_print_error "Invalid tool name specified: ${tool}" - emucon_abort -v ;; - esac - done -fi - -emucon_check_required_arg '-o/--output-dir' "${outdir}" -emucon_ensure_dir_exists "${outdir}" - -# Docker args -image='ubuntu:18.04' -dstdir='/emucon-scripts' -scriptvol="$(emucon_get_current_dir)/scripts:${dstdir}:ro" -outvol="${outdir}:/emucon-output" -cmd="${dstdir}/build.sh ${tools}" - -emucon_print_info 'Running container for building...' -sudo docker run -t --rm -v "${scriptvol}" -v "${outvol}" "${image}" ${cmd} -emucon_print_info "Built files copied to: ${outdir}" - diff --git a/emucon-tools/builder/commands/tool/scripts/build-image-tools.sh b/emucon-tools/builder/commands/tool/scripts/build-image-tools.sh deleted file mode 100644 index 88c0d51..0000000 --- a/emucon-tools/builder/commands/tool/scripts/build-image-tools.sh +++ /dev/null @@ -1,21 +0,0 @@ -# -# Builds image-tools binaries -# - -__print_message 'Downloading OCI image-tools...' -dstdir="${GOPATH}/src/github.com/opencontainers/image-tools" -url='https://github.com/opencontainers/image-tools.git' -tag='v1.0.0-rc3' -mkdir -v -p "${dstdir}" -cd "${dstdir}" -git clone --depth 1 --branch "${tag}" "${url}" . - -__print_message 'Building OCI image-tools...' -go mod init -go mod tidy -go mod vendor -make tool man - -__print_message 'Installing OCI image-tools...' -make install PREFIX="${HOME}/.local" BINDIR="${GOBIN}" - diff --git a/emucon-tools/builder/commands/tool/scripts/build-runc.sh b/emucon-tools/builder/commands/tool/scripts/build-runc.sh deleted file mode 100644 index 8acdbc7..0000000 --- a/emucon-tools/builder/commands/tool/scripts/build-runc.sh +++ /dev/null @@ -1,18 +0,0 @@ -# -# Builds runc binary -# - -__print_message 'Downloading OCI runc...' -dstdir="${GOPATH}/src/github.com/opencontainers/runc" -url='https://github.com/opencontainers/runc.git' -tag='v1.1.4' -mkdir -v -p "${dstdir}" -cd "${dstdir}" -git clone --depth 1 --branch "${tag}" "${url}" . - -__print_message 'Building OCI runc...' -make BUILDTAGS='' - -__print_message 'Installing OCI runc...' -make install PREFIX="${HOME}/.local" BINDIR="${GOBIN}" - diff --git a/emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh b/emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh deleted file mode 100644 index 2c4210d..0000000 --- a/emucon-tools/builder/commands/tool/scripts/build-runtime-tools.sh +++ /dev/null @@ -1,21 +0,0 @@ -# -# Builds runtime-tools binaries -# - -__print_message 'Downloading OCI runtime-tools...' -dstdir="${GOPATH}/src/github.com/opencontainers/runtime-tools" -url='https://github.com/opencontainers/runtime-tools.git' -tag='v0.9.0' -mkdir -v -p "${dstdir}" -cd "${dstdir}" -git clone --depth 1 --branch "${tag}" "${url}" . - -__print_message 'Building OCI runtime-tools...' -go mod init -go mod tidy -go mod vendor -make tool man - -__print_message 'Installing OCI runtime-tools...' -make install PREFIX="${HOME}/.local" BINDIR="${GOBIN}" - diff --git a/emucon-tools/builder/commands/tool/scripts/build.sh b/emucon-tools/builder/commands/tool/scripts/build.sh deleted file mode 100755 index d37a319..0000000 --- a/emucon-tools/builder/commands/tool/scripts/build.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -set -e - -__print_message() -{ - echo "[container] $1" -} - -__print_message "Executing script $0..." - -# Tools to build -if [ -z "$*" ]; then - __print_message 'No tools spcecified! Exiting...' - exit 1 -fi - -readonly outdir='/emucon-output' -readonly scripts="$(dirname $(readlink -f $0))" - -. "${scripts}/prepare-go.sh" - -# Build all requested tools -for tool in "$@" ; do - . "${scripts}/build-${tool}.sh" -done - -__print_message 'Copying output files...' -cp -v -r -t "${outdir}" ${HOME}/.local/* - -__print_message "Script $0 finished" - diff --git a/emucon-tools/builder/commands/tool/scripts/prepare-go.sh b/emucon-tools/builder/commands/tool/scripts/prepare-go.sh deleted file mode 100644 index c2828e9..0000000 --- a/emucon-tools/builder/commands/tool/scripts/prepare-go.sh +++ /dev/null @@ -1,19 +0,0 @@ -# -# Prepares GO toolchain -# - -__print_message 'Installing GO toolchain...' -apt-get update -apt-get install -y git curl golang go-md2man -curl -L "https://go.dev/dl/go1.19.linux-amd64.tar.gz" | tar xz -C /usr/local - -__print_message 'Setting up GO toolchain...' -mkdir -p "${HOME}/.local/bin" -cat >> "${HOME}/.profile" <<- "EOF" - export GOPATH=${HOME}/work - export GOBIN=${HOME}/.local/bin - export PATH="/usr/local/go/bin:${GOBIN}:${PATH}" - export MANPATH=/usr/local/go/share/man:${HOME}/.local/share/man:"${MANPATH}" -EOF - -. "${HOME}/.profile" diff --git a/emucon-tools/builder/emucon-build b/emucon-tools/builder/emucon-build deleted file mode 100755 index aee2dca..0000000 --- a/emucon-tools/builder/emucon-build +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh - -export __cmd_name=$(basename $0) - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} [...] - - DESCRIPTION: - Builds the specified tools or container layers. - - ARGUMENTS: - - Name of the command to execute: - layer - Build a container layer - tool - Build an OCI tool - - EOT -} - - -# ========== Script's Begin ========== - -. emucon-init.sh - -if [ $# -eq 0 ]; then - __print_usage - emucon_exit -fi - -# Parse script's command line arguments -case "$1" in - -h|--help) - __print_usage - emucon_exit ;; - layer|tool) - cmd="$1" - shift 1 ;; - -*) - emucon_print_error "${__cmd_name}: unrecognized option '$1'" - emucon_print_cmdargs_parsing_error - emucon_abort -v ;; - *) - emucon_print_error "${__cmd_name}: invalid command -- '$1'" - emucon_print_cmdargs_parsing_error - emucon_abort -v ;; -esac - -# Running subcommand -cd "commands/${cmd}" -./run.sh "$@" - diff --git a/emucon-tools/install.sh b/emucon-tools/install.sh deleted file mode 100755 index 5182095..0000000 --- a/emucon-tools/install.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/sh - -__cmd_name=$(basename "$0") - -# Default install directory -readonly default_dstdir='/usr/local' - -# Default sudoers directory -readonly default_sudodir='/etc/sudoers.d' - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} [-u ] [--sudoers-dir ] [--destination ] - - DESCRIPTION: - Installs emucon-tools and dependencies on the host system. - - OPTIONS: - -d, --destination - The path to install into. (default: ${default_dstdir}) - - -u, --user - The name of the user for sudoers configuration. - - --sudoers-dir - The path for sudoers drop-in files. (default: ${default_sudodir}) - - EOT -} - - -# ========== Script's Begin ========== - -if ! which emucon-install > /dev/null ; then - echo 'Required emucon-tools are not in PATH!' - echo 'Please source bootstrap.sh first.' - echo 'Aborting...' - exit 1 -fi - -. emucon-init.sh - -# Parse script's command line arguments -longopts='destination:,user:,sudoers-dir:,help' -cmdargs=$(emucon_parse_cmdargs -s 'd:u:h' -l "${longopts}" -- "$@") -if emucon_cmd_failed ; then - emucon_abort -fi - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while true ; do - case "$1" in - -d|--destination) - dstdir="$2" - shift 2 ;; - -u|--user) - user="$2" - shift 2 ;; - --sudoers-dir) - sudodir="$2" - shift 2 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v ;; - esac -done - -# Safety check! -if [ $# -ne 0 ] ; then - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v -fi - -# Installer scripts directory -srcdir=$(emucon_get_current_dir "$0")/installer - -# Install directory -dstdir="${dstdir:-${default_dstdir}}" -emucon_ensure_dir_exists "${dstdir}" - -# Sudoers directory -sudodir="${sudodir:-${default_sudodir}}" -emucon_ensure_dir_exists "${sudodir}" - -# Sudoers user -user="${user:-$(id --user --name)}" - -${srcdir}/install-oci-tools.sh --destination "${dstdir}" || emucon_abort -${srcdir}/install-scripts.sh --user "${user}" --sudoers-dir "${sudodir}" --destination "${dstdir}" || emucon_abort -${srcdir}/install-deps.sh || emucon_abort - diff --git a/emucon-tools/installer/install-deps.sh b/emucon-tools/installer/install-deps.sh deleted file mode 100755 index d93aa14..0000000 --- a/emucon-tools/installer/install-deps.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -__cmd_name=$(basename "$0") - - -# ========== Script's Begin ========== - -. emucon-init.sh - -emucon_print_info 'Installing dependencies...' -sudo apt-get update -sudo apt-get install jq - diff --git a/emucon-tools/installer/install-oci-tools.sh b/emucon-tools/installer/install-oci-tools.sh deleted file mode 100755 index 960ccba..0000000 --- a/emucon-tools/installer/install-oci-tools.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/sh - -__cmd_name=$(basename "$0") - -# Default install directory -readonly default_dstdir='/usr/local' - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} [--destination ] - - DESCRIPTION: - Builds and installs OCI-Tools. - - OPTIONS: - -d, --destination - The path to install into. (default: ${default_dstdir}) - - EOT -} - - -# ========== Script's Begin ========== - -if ! which emucon-install > /dev/null ; then - echo 'Required emucon-tools are not in PATH!' - echo 'Please source bootstrap.sh first.' - echo 'Aborting...' - exit 1 -fi - -. emucon-init.sh - -# Parse script's command line arguments -longopts='destination:,help' -cmdargs=$(emucon_parse_cmdargs -s 'd:h' -l "${longopts}" -- "$@") -if emucon_cmd_failed ; then - emucon_abort -fi - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while true ; do - case "$1" in - -d|--destination) - dstdir="$2" - shift 2 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v ;; - esac -done - -# Safety check! -if [ $# -ne 0 ] ; then - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v -fi - -# Runtime directory -curdir=$(emucon_get_current_dir "$0") - -# Install directory -dstdir="${dstdir:-${default_dstdir}}" -emucon_ensure_dir_exists "${dstdir}" - -emucon_print_info 'Creating temporary directory...' -readonly tmpdir=$(mktemp -d '/tmp/emucon-XXX') - -__cleanup() -{ - emucon_print_info 'Cleaning up...' - sudo rm -r -v "${tmpdir}" -} - -# Setup an exit-trap -trap __cleanup EXIT - -emucon_print_info 'Building OCI tools...' -cd "${curdir}/../builder" -./emucon-build tool --output-dir "${tmpdir}" || emucon_abort - -emucon_print_info 'Installing OCI tools...' -emucon-install "${tmpdir}" "${dstdir}" || emucon_abort - diff --git a/emucon-tools/installer/install-scripts.sh b/emucon-tools/installer/install-scripts.sh deleted file mode 100755 index 50d89b8..0000000 --- a/emucon-tools/installer/install-scripts.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/sh - -__cmd_name=$(basename "$0") - -# Default install directory -readonly default_dstdir='/usr/local' - -# Default sudoers directory -readonly default_sudodir='/etc/sudoers.d' - - -# ========== Helper Functions ========== - -__print_usage() -{ - cat <<- EOT - USAGE: - ${__cmd_name} [-u ] [--sudoers-dir ] [--destination ] - - DESCRIPTION: - Installs emucon-tools and dependencies on the host system. - - OPTIONS: - -d, --destination - The path to install into. (default: ${default_dstdir}) - - -u, --user - The name of the user for sudoers configuration. - - --sudoers-dir - The path for sudoers drop-in files. (default: ${default_sudodir}) - - EOT -} - - -# ========== Script's Begin ========== - -if ! which emucon-install > /dev/null ; then - echo 'Required emucon-tools are not in PATH!' - echo 'Please source bootstrap.sh first.' - echo 'Aborting...' - exit 1 -fi - -. emucon-init.sh - -# Parse script's command line arguments -longopts='destination:,user:,sudoers-dir:,help' -cmdargs=$(emucon_parse_cmdargs -s 'd:u:h' -l "${longopts}" -- "$@") -if emucon_cmd_failed ; then - emucon_abort -fi - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while true ; do - case "$1" in - -d|--destination) - dstdir="$2" - shift 2 ;; - -u|--user) - user="$2" - shift 2 ;; - --sudoers-dir) - sudodir="$2" - shift 2 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v ;; - esac -done - -# Safety check! -if [ $# -ne 0 ] ; then - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort -v -fi - -# Runtime directory -curdir=$(emucon_get_current_dir) -srcdir=$(emucon_to_absolute_path "${curdir}/../runtime") - -# Install directory -dstdir="${dstdir:-${default_dstdir}}" -emucon_ensure_dir_exists "${dstdir}" - -# Sudoers directory -sudodir="${sudodir:-${default_sudodir}}" -emucon_ensure_dir_exists "${sudodir}" - -# Sudoers user -user="${user:-$(id --user --name)}" - -emucon_print_info 'Creating temporary directory...' -readonly tmpdir=$(mktemp -d '/tmp/emucon-XXX') - -__cleanup() -{ - emucon_print_info 'Cleaning up...' - sudo rm -r -v "${tmpdir}" -} - -# Setup an exit-trap -trap __cleanup EXIT - -emucon_print_info 'Installing emucon-tools...' -emucon-install "${srcdir}" "${dstdir}" || emucon_abort - -emucon_print_info "Installing sudoers configuration for user '${user}'..." -sudosrc="$(emucon-paths sudoers.template)" -sudotmp="${tmpdir}/emucon-pwdless-commands" -sedcmds="s|{{install-dir}}|${dstdir}|g; s|{{user}}|${user}|g" -cat "${sudosrc}" | sed "${sedcmds}" > "${sudotmp}" || emucon_abort -visudo --check --file "${sudotmp}" || emucon_abort -if [ ! -w "${sudodir}" ] ; then - # Install directory not writable, - # use sudo for file operations - cmdprefix='sudo' -fi - -# TODO: skip installing it for now! A different solution is needed! -#${cmdprefix} install -v -m 440 "${sudotmp}" "${sudodir}" || emucon_abort diff --git a/emucon-tools/runtime/lib/emucon/helpers.sh b/emucon-tools/runtime/lib/emucon/helpers.sh deleted file mode 100644 index 6b1ec90..0000000 --- a/emucon-tools/runtime/lib/emucon/helpers.sh +++ /dev/null @@ -1,175 +0,0 @@ -# -# Helpers for emucon-tools, compatible with /bin/sh -# - -# ========== Helper Functions ========== - -emucon_exit() -{ - exit 0 -} - -emucon_abort() -{ - if [ "$1" = '-v' ] ; then - emucon_print_error 'Aborting...' - fi - - exit 1 -} - -emucon_cmd_failed() -{ - # Failed, if exit code != 0 - test $? -ne 0 -} - -emucon_print() -{ - echo "$*" -} - -emucon_print_info() -{ - echo "[${__cmd_name}] $*" -} - -emucon_print_warning() -{ - if [ -n "${TERM}" ] ; then - # Output in yellow color! - tput setaf 3 - emucon_print_info "$@" - tput sgr0 - else - emucon_print_info "$@" - fi -} - -emucon_print_error() -{ - if [ -n "${TERM}" ] ; then - # Output in red color! - tput setaf 1 - emucon_print_info "$@" >&2 - tput sgr0 - else - emucon_print_info "$@" >&2 - fi -} - -emucon_print_invalid_cmdargs_error() -{ - emucon_print_error "Invalid command line arguments: $1" -} - -emucon_print_cmdargs_parsing_error() -{ - emucon_print_error 'Parsing command line arguments failed!' - emucon_print_error "Run '${__cmd_name} --help' for usage documentation." -} - -emucon_parse_cmdargs() -{ - # Example call: - # emucon_parse_cmdargs -s 'o:h' -l 'output:,help' -- args - - local shortopts - local longopts - - # Parse function's named arguments - while true ; do - case "$1" in - -s|--short) - shortopts="$2" - shift 2 ;; - -l|--long) - longopts="$2" - shift 2 ;; - --) - shift 1 - break ;; - *) - emucon_print_error "emucon_parse_cmdargs: unrecognized option '$1'" - emucon_print_error 'Internal error occured! Aborting...' - emucon_abort - esac - done - - local cmdargs - - # Parse supplied command's arguments - cmdargs=$(getopt -o "${shortopts}" -l "${longopts}" -n "${__cmd_name}" -- "$@") - if emucon_cmd_failed ; then - emucon_print_cmdargs_parsing_error - emucon_abort -v - fi - - printf "%s" "${cmdargs}" -} - -emucon_check_required_arg() -{ - local argname - local argvalue - - argname="$1" - argvalue="$2" - - if [ -z "${argvalue}" ] ; then - emucon_print_error "Required argument ${argname} is missing!" - emucon_abort -v - fi -} - -emucon_to_absolute_path() -{ - if [ -f "$1" ] ; then - local dname="$(dirname "$1")" - local fname="/$(basename "$1")" - else - local dname="$1" - fi - - dname="$(cd "${dname}" && pwd)" - printf '%s%s\n' "${dname}" "${fname}" -} - -emucon_to_absolute_dirname() -{ - echo $(dirname $(emucon_to_absolute_path "$1")) -} - -emucon_get_current_dir() -{ - echo $(emucon_to_absolute_dirname "$0") -} - -emucon_ensure_dir_exists() -{ - if [ ! -d "$1" ] ; then - emucon_print_error "Specified directory does not exist: $1" - emucon_abort -v - fi -} - -emucon_ensure_file_exists() -{ - if [ ! -f "$1" ] ; then - emucon_print_error "Specified file does not exist: $1" - emucon_abort -v - fi -} - -emucon_ensure_is_installed() -{ - local cmd - cmd="$1" - - if ! which "${cmd}" > /dev/null ; then - emucon_print_error "${cmd} was not found in PATH!" - emucon_print_error 'Please install it first.' - emucon_abort -v - fi -} - diff --git a/emucon-tools/runtime/lib/emucon/qcow-create.sh b/emucon-tools/runtime/lib/emucon/qcow-create.sh deleted file mode 100644 index 636f60f..0000000 --- a/emucon-tools/runtime/lib/emucon/qcow-create.sh +++ /dev/null @@ -1,87 +0,0 @@ -# -# Implementation: emucon-qcow create -# - -__cmd_name="${__cmd_name} create" - - -# ========== Helper Functions ========== - -__print_usage() { - cat <<- EOT - USAGE: - ${__cmd_name} -o - - DESCRIPTION: - Creates a qcow-image. - - OPTIONS: - -o, --options - List of image options. - - ARGUMENTS: - - Path of the image to create. - - EOT -} - -__has_option() { - local name="$1" - local list="$2" - echo "${list}" | grep "${name}=" > /dev/null -} - - -# ========== Script's Begin ========== - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse subcommand's command line arguments -longopts='options:,help' -cmdargs=$(emucon_parse_cmdargs -s 'o:,h' -l "${longopts}" -- "$@") || emucon_abort - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while [ $# -gt 0 ] ; do - case "$1" in - -o|--options) - options="$2" - shift 1 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort ;; - esac - shift 1 -done - -outpath="$1" - -emucon_check_required_arg '' "${outpath}" -emucon_ensure_is_installed 'qemu-img' - -# Option 'size' is required, when option 'backing_file' is not used! -if ! __has_option 'size' "${options}" && ! __has_option 'backing_file' "${options}" ; then - emucon_print_error "Size of the qcow-image is missing!" - emucon_print_error "It must be specified in the options list, like:" - if [ -n "${options}" ] ; then - options="${options},size=2G" - else - options="size=2G" - fi - emucon_print_error "${__cmd_name} -o ${options} ${outpath}" - emucon_abort -v -fi - -emucon_print_info "Creating qcow-image..." -qemu-img create -f qcow2 -o "${options}" "${outpath}" - diff --git a/emucon-tools/runtime/lib/emucon/qcow-mkfs.sh b/emucon-tools/runtime/lib/emucon/qcow-mkfs.sh deleted file mode 100644 index 00a739f..0000000 --- a/emucon-tools/runtime/lib/emucon/qcow-mkfs.sh +++ /dev/null @@ -1,88 +0,0 @@ -# -# Implementation: emucon-qcow mkfs -# - -__cmd_name="${__cmd_name} mkfs" - - -# ========== Helper Functions ========== - -__print_usage() { - cat <<- EOT - USAGE: - ${__cmd_name} --fs-type - - DESCRIPTION: - Creates a new filesystem on specified qcow-image. - - OPTIONS: - -t, --fs-type - Filesystem (ext4, xfs, etc.) to create. - - ARGUMENTS: - - Path of the image to create filesystem on. - - EOT -} - - -# ========== Script's Begin ========== - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse subcommand's command line arguments -cmdargs=$(emucon_parse_cmdargs -s 't:h' -l 'fs-type:,help' -- "$@") || emucon_abort - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while [ $# -gt 0 ] ; do - case "$1" in - -t|--fs-type) - fstype="$2" - shift 1 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort ;; - esac - shift 1 -done - -imgpath="$1" - -emucon_check_required_arg '-t/--fs-type' "${fstype}" -emucon_check_required_arg '' "${imgpath}" -emucon_ensure_is_installed "mkfs.${fstype}" - -emucon_print_info "Creating mountpoint for qcow-image..." -mntpath=$(mktemp -d --tmpdir 'emucon-XXXXX') - -__cleanup() { - emucon_print_info 'Cleaning up...' - emucon-qcow unmount "${mntpath}" - rm -r -v "${mntpath}" -} - -trap __cleanup EXIT - -emucon-qcow mount "${imgpath}" "${mntpath}" || emucon_abort -rawimgpath=$(__to_mounted_image_path "${imgpath}" "${mntpath}") - -emucon_print_info "Creating filesystem..." -case "${fstype}" in - ext4) - mkfs.ext4 -F "${rawimgpath}" || emucon_abort -v ;; - *) - emucon_print_error "Unsupported filesystem: ${fstype}" - emucon_abort -v ;; -esac - diff --git a/emucon-tools/runtime/lib/emucon/qcow-mount.sh b/emucon-tools/runtime/lib/emucon/qcow-mount.sh deleted file mode 100644 index 663f437..0000000 --- a/emucon-tools/runtime/lib/emucon/qcow-mount.sh +++ /dev/null @@ -1,133 +0,0 @@ -# -# Implementation: emucon-qcow mount -# - -__cmd_name="${__cmd_name} mount" - - -# ========== Helper Functions ========== - -__print_usage() { - cat <<- EOT - USAGE: - ${__cmd_name} [--fs-type --fs-path ] - - DESCRIPTION: - Mounts a qcow-image at the specified mount-point. - - OPTIONS: - --fs-type - Filesystem (ext4, xfs, etc.) to mount at , if the qcow-image contains one. - - --fs-path - Path to mount the qcow-image's filesystem at. - - ARGUMENTS: - - Path of the image to mount. - - - Mount-point for the image. - - EOT -} - - -# ========== Script's Begin ========== - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse subcommand's command line arguments -longopts='fs-type:,fs-path:,help' -cmdargs=$(emucon_parse_cmdargs -s 'h' -l "${longopts}" -- "$@") || emucon_abort - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while [ $# -gt 0 ] ; do - case "$1" in - --fs-type) - fstype="$2" - shift 1 ;; - --fs-path) - fspath="$2" - shift 1 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort ;; - esac - shift 1 -done - -imgpath="$1" -mntpath="$2" - -emucon_check_required_arg '' "${imgpath}" -emucon_check_required_arg '' "${mntpath}" - -emucon_print_info "Mounting qcow-image at ${mntpath}..." -case "${mntpath}" in - /dev/nbd*) - # NBD mode - emucon_ensure_is_installed 'qemu-nbd' - - sudo qemu-nbd --discard unmap \ - --cache writeback \ - --connect "${mntpath}" \ - "${imgpath}" || emucon_abort -v - ;; - *) - # xmount mode - emucon_ensure_is_installed 'xmount' - emucon_ensure_is_installed 'fusermount' - - xmount --out raw \ - --in qemu "${imgpath}" \ - --inopts 'qemuwritable=true,bdrv_cache=writeback' \ - --cache writethrough \ - "${mntpath}" || emucon_abort -v - ;; -esac - -__cleanup() { - if [ $? -eq 0 ] ; then - # Normal termination - exit 0 - fi - - emucon_print_info "Un-mounting qcow-image at ${mntpath}..." - case "${mntpath}" in - /dev/nbd*) - sudo qemu-nbd --disconnect "${mntpath}" ;; - *) - fusermount -u -z "${mntpath}" ;; - esac -} - -trap __cleanup EXIT - -if [ -n "${fstype}" ] ; then - emucon_check_required_arg '--fs-path' "${fspath}" - - emucon_print_info "Mounting qcow-image's filesystem at ${fspath}..." - case "${mntpath}" in - /dev/nbd*) - # System-mount mode - sudo mount -t "${fstype}" "${mntpath}" "${fspath}" - sudo chmod a+rwx "${fspath}" ;; - *) - # FUSE-mount mode - emucon_ensure_is_installed 'lklfuse' - rawimgpath=$(__to_mounted_image_path "${imgpath}" "${mntpath}") - lklfuse "${rawimgpath}" "${fspath}" -o "allow_other,use_ino,rw,type=${fstype}" ;; - esac -fi - diff --git a/emucon-tools/runtime/lib/emucon/qcow-unmount.sh b/emucon-tools/runtime/lib/emucon/qcow-unmount.sh deleted file mode 100644 index 1151605..0000000 --- a/emucon-tools/runtime/lib/emucon/qcow-unmount.sh +++ /dev/null @@ -1,103 +0,0 @@ -# -# Implementation: emucon-qcow unmount -# - -__cmd_name="${__cmd_name} unmount" - - -# ========== Helper Functions ========== - -__print_usage() { - cat <<- EOT - USAGE: - ${__cmd_name} [--fs-path ] - - DESCRIPTION: - Un-mounts a qcow-image from the specified mount-point. - - OPTIONS: - --fs-path - Path to unmount the qcow-image's filesystem from. - - --no-sync - Do not execute a sync after unmount. - - ARGUMENTS: - - Mount-point for the image. - - EOT -} - -__run_sync() { - test "${dosync}" = 'y' && sync -} - - -# ========== Script's Begin ========== - -if [ $# -eq 0 ] ; then - __print_usage - emucon_exit -fi - -# Parse subcommand's command line arguments -longopts='fs-path:,no-sync,help' -cmdargs=$(emucon_parse_cmdargs -s 'h' -l "${longopts}" -- "$@") || emucon_abort - -dosync='y' - -# Lookup parsed parameters and their arguments -eval set -- ${cmdargs} -while [ $# -gt 0 ] ; do - case "$1" in - --fs-path) - fspath="$2" - shift 1 ;; - --no-sync) - dosync='n' - shift 1 ;; - -h|--help) - __print_usage - emucon_exit ;; - --) - shift 1 - break ;; - *) - emucon_print_invalid_cmdargs_error "${cmdargs}" - emucon_abort ;; - esac - shift 1 -done - -mntpath="$1" - -emucon_check_required_arg '' "${mntpath}" - -if [ -n "${fspath}" ] ; then - __run_sync - emucon_print_info "Un-mounting qcow-image's filesystem from ${fspath}..." - emucon_ensure_dir_exists "${fspath}" - case "${mntpath}" in - /dev/nbd*) - sudo umount "${fspath}" ;; - *) - emucon_ensure_is_installed 'fusermount' - fusermount -u -z "${fspath}" ;; - esac -fi - -__run_sync - -emucon_print_info "Un-mounting qcow-image from ${mntpath}..." -case "${mntpath}" in - /dev/nbd*) - emucon_ensure_is_installed 'qemu-nbd' - sudo qemu-nbd --disconnect "${mntpath}" ;; - *) - emucon_ensure_is_installed 'fusermount' - fusermount -u -z "${mntpath}" ;; -esac - -__run_sync - diff --git a/emucon-tools/runtime/share/emucon/sudoers.template b/emucon-tools/runtime/share/emucon/sudoers.template deleted file mode 100644 index dba3a89..0000000 --- a/emucon-tools/runtime/share/emucon/sudoers.template +++ /dev/null @@ -1,48 +0,0 @@ -# -# Privileged commands for emucon-tools, -# required to be run without password -# - -# Allowed mount/umount calls -Cmnd_Alias MOUNT_COMMANDS = /bin/mount -t overlay *, \ - /bin/umount - - -# Allowed rm command calls -Cmnd_Alias RM_COMMANDS = /bin/rm -[rv] -[rv] /tmp/eaas-*, \ - /bin/rm -[rv] /tmp/eaas-*, \ - /bin/rm /tmp/eaas-* - - -# Allowed chown command calls -Cmnd_Alias CHOWN_COMMANDS = /bin/chown --recursive {{user}}\:{{user}} /tmp/eaas-*, \ - /bin/chown -R {{user}}\:{{user}} /tmp/eaas-*, \ - /bin/chown --recursive {{user}}\:{{user}} state, \ - /bin/chown -R {{user}}\:{{user}} state - - -# Allowed runc command calls -Cmnd_Alias RUNC_COMMANDS = {{install-dir}}/bin/runc --* run *, \ - {{install-dir}}/bin/runc run *, \ - {{install-dir}}/bin/runc --* pause *, \ - {{install-dir}}/bin/runc pause *, \ - {{install-dir}}/bin/runc --* resume *, \ - {{install-dir}}/bin/runc resume *, \ - {{install-dir}}/bin/runc --* checkpoint *, \ - {{install-dir}}/bin/runc checkpoint *, \ - {{install-dir}}/bin/runc --* restore *, \ - {{install-dir}}/bin/runc restore *, \ - {{install-dir}}/bin/runc --* exec *, \ - {{install-dir}}/bin/runc exec *, \ - {{install-dir}}/bin/runc --* list *, \ - {{install-dir}}/bin/runc list *, \ - {{install-dir}}/bin/runc --* list, \ - {{install-dir}}/bin/runc list, \ - {{install-dir}}/bin/runc --* kill *, \ - {{install-dir}}/bin/runc kill *, \ - {{install-dir}}/bin/runc --* ps *, \ - {{install-dir}}/bin/runc ps * - - -{{user}} ALL = NOPASSWD: MOUNT_COMMANDS, RUNC_COMMANDS, RM_COMMANDS, CHOWN_COMMANDS - From 523272dea228d157975a173d69f8a681ea020902 Mon Sep 17 00:00:00 2001 From: peeqle Date: Mon, 23 Jun 2025 11:12:24 +0300 Subject: [PATCH 5/5] removed gitactions emucon build --- .github/workflows/docker-image.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8b73f86..6544037 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -18,15 +18,6 @@ jobs: image_tag: ${{ steps.meta.outputs.image_tag }} steps: - uses: actions/checkout@v4 - - - name: Create /tmp/oci-tools directory on runner - run: mkdir /tmp/oci-tools - - - name: Prepare and Build OCI tools on runner - run: | - cd emucon-tools/ - . ./bootstrap.sh - ./installer/install-oci-tools.sh --destination /tmp/oci-tools - name: Extract metadata (tags, labels) id: meta