package com.github.ruediste1.btrbck; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ProcessBuilder.Redirect; import java.nio.file.Path; import java.util.Arrays; import java.util.LinkedList; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import com.github.ruediste1.btrbck.SyncService.SendFileSpec; import com.github.ruediste1.btrbck.dom.Snapshot; /** * Encapsulates BTRFS operations */ @Singleton public class BtrfsService { Logger log = LoggerFactory.getLogger(BtrfsService.class); private ThreadLocal<Boolean> useSudo = new ThreadLocal<>(); public void setUseSudo(boolean value) { useSudo.set(value); } private boolean useSudo() { return Boolean.TRUE.equals(useSudo.get()); } private ThreadLocal<Boolean> useStrace = new ThreadLocal<>(); public void setUseStrace(boolean value) { useStrace.set(value); } private boolean useStrace() { return Boolean.TRUE.equals(useStrace.get()); } public void createSubVolume(Path subVolumeDir) { String path = subVolumeDir.toAbsolutePath().toString(); try { { int exitValue = processBuilder("btrfs", "subvolume", "create", path).start().waitFor(); if (exitValue != 0) { throw new IOException("exit code: " + exitValue); } } // determine the current user String userName; { Process process = new ProcessBuilder("whoami").redirectError( Redirect.INHERIT).start(); int exitValue = process.waitFor(); if (exitValue != 0) { throw new RuntimeException("whoami exited with " + exitValue); } userName = Util.readFully(process.getInputStream()); if (userName.endsWith("\n")) { userName = userName.substring(0, userName.length() - "\n".length()); } } // change the owner of the subvolume { int exitValue = processBuilder("chown", userName + ":", path) .start().waitFor(); if (exitValue != 0) { throw new RuntimeException("chown exited with " + exitValue); } } } catch (IOException | InterruptedException e) { throw new RuntimeException("Error while creating sub volume in " + path, e); } } private ProcessBuilder processBuilder(String... commands) { LinkedList<String> list = new LinkedList<String>( Arrays.asList(commands)); return processBuilder(list); } private ProcessBuilder processBuilder(LinkedList<String> list) { if (useStrace()) { list.addFirst("-ff"); list.addFirst("strace." + MDC.get("id") + ".log"); list.addFirst("-o"); list.addFirst("strace"); } if (useSudo()) { list.addFirst("sudo"); } log.debug("created process builder: " + list); return new ProcessBuilder().redirectError(Redirect.INHERIT) .redirectOutput(Redirect.INHERIT).command(list); } public void deleteSubVolume(Path subVolumeDir) { String path = subVolumeDir.toAbsolutePath().toString(); try { int exitValue = processBuilder("btrfs", "subvolume", "delete", path) .start().waitFor(); if (exitValue != 0) { throw new IOException("exit code: " + exitValue); } } catch (IOException | InterruptedException e) { throw new RuntimeException("Error while deleting sub volume in " + path, e); } } public void receive(Path destinationPath, Consumer<OutputStream> callback) { try { Process process = processBuilder("btrfs", "receive", "-e", destinationPath.toAbsolutePath().toString()) .redirectOutput(Redirect.PIPE).start(); callback.consume(process.getOutputStream()); process.getOutputStream().close(); int exitValue = process.waitFor(); if (exitValue != 0) { throw new IOException("exit code: " + exitValue); } } catch (IOException | InterruptedException e) { throw new RuntimeException( "Error while receiving snapshot sub volume in " + destinationPath, e); } } public void send(SendFileSpec sendFile, Consumer<InputStream> callback) { try { LinkedList<String> args = new LinkedList<>(); args.addAll(Arrays.asList("btrfs", "send")); { boolean firstSource = true; for (Snapshot s : sendFile.cloneSources) { if (firstSource) { args.add("-p"); firstSource = false; } else { args.add("-c"); } args.add(s.getSnapshotDir().toAbsolutePath().toString()); } } args.add(sendFile.target.getSnapshotDir().toAbsolutePath() .toString()); Process process = processBuilder(args) .redirectOutput(Redirect.PIPE).start(); callback.consume(process.getInputStream()); int exitValue = process.waitFor(); if (exitValue != 0) { throw new IOException("exit code: " + exitValue); } } catch (IOException | InterruptedException e) { throw new RuntimeException( "Error while sending snapshot sub volume in " + sendFile, e); } } /** * Takes a read only snapshot of the source an puts it to the target * * @param readonly * TODO */ public void takeSnapshot(Path sourceVolume, Path target, boolean readonly) { try { { LinkedList<String> list = new LinkedList<String>(); list.addAll(Arrays.asList("btrfs", "subvolume", "snapshot")); if (readonly) { list.add("-r"); } list.add(sourceVolume.toAbsolutePath().toString()); list.add(target.toAbsolutePath().toString()); int exitValue = processBuilder(list).start().waitFor(); if (exitValue != 0) { throw new IOException("exit code: " + exitValue); } } } catch (IOException | InterruptedException e) { throw new RuntimeException("Error while taking snapshot of " + sourceVolume.toAbsolutePath() + " to " + target.toAbsolutePath(), e); } } }