io.github.mike10004.debutils.SubprocessDebAnalyst Maven / Gradle / Ivy
package io.github.mike10004.debutils;
import io.github.mike10004.subprocess.ProcessResult;
import io.github.mike10004.subprocess.ScopedProcessTracker;
import io.github.mike10004.subprocess.Subprocess;
import org.apache.commons.io.FileUtils;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
/**
* Service class that analyzes a deb file.
*/
class SubprocessDebAnalyst implements DebAnalyst {
private final File debFile;
public SubprocessDebAnalyst(File debFile) {
this.debFile = requireNonNull(debFile, "debFile");
}
@Override
public DebExtraction extract(Path persistentDir) throws IOException {
DebExtraction extraction = new Extractor(persistentDir).extract();
return extraction;
}
private class Extractor {
private final Path destination;
public Extractor(Path destination) {
this.destination = destination;
}
public DebExtraction extract() throws IOException {
Subprocess s = Subprocess.running("dpkg")
.arg("--extract")
.arg(debFile.getAbsolutePath())
.arg(destination.toString())
.build();
ProcessResult presult;
try (ScopedProcessTracker tracker = new ScopedProcessTracker()) {
presult = s.launcher(tracker)
.outputStrings(Charset.defaultCharset())
.launch().await(30, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException e) {
throw new RuntimeException("failed to await result of dpkg --extract", e);
}
if (presult.exitCode() != 0) {
throw new IOException(String.format("exit code %s from dpkg --extract: %s", presult.exitCode(), presult.content().stderr()));
}
Collection files = FileUtils.listFiles(destination.toFile(), null, true);
return new DiskDebExtraction(destination, files);
}
}
@Override
public File getDebFile() {
return debFile;
}
@Override
public DebContents contents() throws DpkgDebException {
return new IndexLoader().call();
}
@Override
public DebControl control() throws DpkgDebException {
Path tempdir = null;
try {
tempdir = java.nio.file.Files.createTempDirectory("deb-control-output");
return control(tempdir);
} catch (IOException e) {
throw new DpkgDebException(e);
} finally {
if (tempdir != null) {
try {
FileUtils.deleteDirectory(tempdir.toFile());
} catch (IOException e) {
LoggerFactory.getLogger(getClass()).warn("failed to delete temporary directory at " + tempdir);
}
}
}
}
@Override
public DebControl control(Path scratchDir) throws DpkgDebException {
return new ControlLoader(scratchDir).call();
}
private static DebContents createIndex(ProcessResult result) {
String stdout = result.content().stdout();
List entries = stdout.lines()
.map(DebEntry::fromLine)
.filter(Objects::nonNull)
.collect(Collectors.toList());
return new BufferedDebContents(entries);
}
private class IndexLoader extends DpkgDebLoader {
public IndexLoader() {
super(Arrays.asList("--contents", debFile.getAbsolutePath()), SubprocessDebAnalyst::createIndex);
}
}
private class ControlLoader extends DpkgDebLoader {
public ControlLoader(Path outputDir) {
super(Arrays.asList("--control", debFile.getAbsolutePath(), outputDir.toString()), result -> {
return extractControl(outputDir, result);
});
}
}
private interface ThrowingFunction {
T apply(F input) throws X;
}
private static Charset controlFileCharset() {
return StandardCharsets.UTF_8;
}
private static DebControl fromOutputDir(Path outputDir) throws IOException {
Map fileMap = new HashMap<>();
for (Path p : java.nio.file.Files.list(outputDir).toArray(Path[]::new)) {
String text = java.nio.file.Files.readString(p, controlFileCharset());
Set permissions = java.nio.file.Files.getPosixFilePermissions(p);
DebControl.PackagingFile f = new DebControl.PackagingFile(text, permissions);
fileMap.put(p.getFileName().toString(), f);
}
return new BufferedDebControl(fileMap);
}
@SuppressWarnings("unused")
private static DebControl extractControl(Path outputDir, ProcessResult result) throws DpkgDebException {
try {
return fromOutputDir(outputDir);
} catch (IOException e) {
throw new DpkgDebException(e);
}
}
public static class DpkgDebException extends Exception {
public DpkgDebException(String message) {
super(message);
}
public DpkgDebException(String message, Throwable cause) {
super(message, cause);
}
public DpkgDebException(Throwable cause) {
super(cause);
}
}
private static class DpkgDebLoader implements Callable {
private final List args;
private final ThrowingFunction, T, DpkgDebException> transform;
public DpkgDebLoader(List args, ThrowingFunction, T, DpkgDebException> transform) {
this.args = requireNonNull(args);
this.transform = requireNonNull(transform);
}
@Override
public T call() throws DpkgDebException {
try (ScopedProcessTracker processTracker = new ScopedProcessTracker()) {
ProcessResult result = Subprocess.running("dpkg-deb")
.args(args)
.build()
.launcher(processTracker)
.outputStrings(Charset.defaultCharset())
.launch().await();
checkState(result.exitCode() == 0, "nonzero exit %s: %s", result.exitCode(), result.content().stderr());
return transform.apply(result);
} catch (InterruptedException | RuntimeException e) {
throw new DpkgDebException(e);
}
}
}
@Override
public DebInfo info() throws DpkgDebException {
return new InfoLoader().call();
}
private static DebInfo createInfo(ProcessResult result) {
return new BufferedDebInfo(result.content().stdout());
}
private class InfoLoader extends DpkgDebLoader {
public InfoLoader() {
super(Arrays.asList("--info", debFile.getAbsolutePath()), SubprocessDebAnalyst::createInfo);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy