io.github.mike10004.debutils.DpkgDebAnalyst 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.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
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.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;
/**
* Analyst implementation that uses {@code dpkg-deb}.
*/
class DpkgDebAnalyst implements DebAnalyst {
private static final Logger log = LoggerFactory.getLogger(DpkgDebAnalyst.class);
private final File debFile;
public DpkgDebAnalyst(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 DebUtilsException {
return new IndexLoader().call();
}
private static class DpkgDebException extends DebUtilsException {
@SuppressWarnings("unused")
public DpkgDebException(String message) {
super(message);
}
@SuppressWarnings("unused")
public DpkgDebException(String message, Throwable cause) {
super(message, cause);
}
public DpkgDebException(Throwable cause) {
super(cause);
}
}
@Override
public DebControl control() throws DebUtilsException {
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 DebUtilsException {
return new ControlLoader(scratchDir).call();
}
private static DebContents createIndex(ProcessResult result) {
String stdout = result.content().stdout();
DebContentsLineParser lineParser = new DebContentsLineParser();
List entries = stdout.lines()
.map(line -> {
try {
return lineParser.parseEntry(line);
} catch (DebUtilsException e) {
log.debug("failed to parse line from contents output: {}", StringUtils.abbreviate(line, 512));
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
return new BufferedDebContents(entries);
}
private class IndexLoader extends DpkgDebLoader {
public IndexLoader() {
super(Arrays.asList("--contents", debFile.getAbsolutePath()), DpkgDebAnalyst::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 DebUtilsException {
try {
return fromOutputDir(outputDir);
} catch (IOException e) {
throw new DpkgDebException(e);
}
}
private static class DpkgDebLoader implements Callable {
private final List args;
private final ThrowingFunction, T, DebUtilsException> transform;
public DpkgDebLoader(List args, ThrowingFunction, T, DebUtilsException> transform) {
this.args = requireNonNull(args);
this.transform = requireNonNull(transform);
}
@Override
public T call() throws DebUtilsException {
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 DebUtilsException {
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()), DpkgDebAnalyst::createInfo);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy