mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-07-08 15:56:09 +12:00
Refactor shells
This commit is contained in:
parent
ac16967efd
commit
5dcb994f9b
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.core.impl;
|
||||
|
||||
import io.xpipe.core.process.ShellProcessControl;
|
||||
import io.xpipe.core.process.ShellTypes;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ public class OutputStreamStore implements StreamDataStore {
|
|||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContentExclusivelyAccessible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFlow getFlow() {
|
||||
return DataFlow.OUTPUT;
|
||||
|
|
|
@ -7,6 +7,8 @@ import java.util.function.Consumer;
|
|||
|
||||
public interface CommandProcessControl extends ProcessControl {
|
||||
|
||||
@Override
|
||||
public CommandProcessControl sensitive();
|
||||
CommandProcessControl complex();
|
||||
|
||||
default InputStream startExternalStdout() throws Exception {
|
||||
|
@ -64,9 +66,7 @@ public interface CommandProcessControl extends ProcessControl {
|
|||
|
||||
String readOnlyStdout() throws Exception;
|
||||
|
||||
public default void discardOrThrow() throws Exception {
|
||||
readOrThrow();
|
||||
}
|
||||
public void discardOrThrow() throws Exception;
|
||||
|
||||
void accumulateStdout(Consumer<String> con);
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import java.nio.charset.Charset;
|
|||
|
||||
public interface ProcessControl extends Closeable, AutoCloseable {
|
||||
|
||||
ProcessControl sensitive();
|
||||
|
||||
String prepareTerminalOpen() throws Exception;
|
||||
|
||||
void closeStdin() throws IOException;
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.core.charsetter.NewLine;
|
|||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@JsonTypeInfo(
|
||||
|
@ -15,15 +16,11 @@ public interface ShellType {
|
|||
|
||||
String getScriptFileEnding();
|
||||
|
||||
default String commandWithVariable(String key, String value, String command) {
|
||||
return joinCommands(getSetVariableCommand(key, value), command);
|
||||
}
|
||||
String addInlineVariablesToCommand(Map<String, String> variables, String command);
|
||||
|
||||
String getPauseCommand();
|
||||
|
||||
String createInitFileContent(String command);
|
||||
|
||||
String getTerminalFileOpenCommand(String file);
|
||||
String prepareScriptContent(String content);
|
||||
|
||||
default String flatten(List<String> command) {
|
||||
return command.stream()
|
||||
|
@ -35,12 +32,6 @@ public interface ShellType {
|
|||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
default String joinCommands(String... s) {
|
||||
return String.join(getConcatenationOperator(), s);
|
||||
}
|
||||
|
||||
void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception;
|
||||
|
||||
default String getExitCommand() {
|
||||
return "exit";
|
||||
}
|
||||
|
@ -61,11 +52,11 @@ public interface ShellType {
|
|||
return getEchoCommand(s, false);
|
||||
}
|
||||
|
||||
String getSetVariableCommand(String variable, String value);
|
||||
|
||||
|
||||
String getEchoCommand(String s, boolean toErrorStream);
|
||||
|
||||
String queryShellProcessId(ShellProcessControl control) throws Exception;
|
||||
|
||||
String getSetVariableCommand(String variableName, String value);
|
||||
|
||||
default String getPrintVariableCommand(String name) {
|
||||
return getPrintVariableCommand("", name);
|
||||
|
@ -79,19 +70,21 @@ public interface ShellType {
|
|||
|
||||
List<String> executeCommandListWithShell(String cmd);
|
||||
|
||||
List<String> createMkdirsCommand(String dirs);
|
||||
List<String> getMkdirsCommand(String dirs);
|
||||
|
||||
String createFileReadCommand(String file);
|
||||
String getFileReadCommand(String file);
|
||||
|
||||
String createFileWriteCommand(String file);
|
||||
String getStreamFileWriteCommand(String file);
|
||||
|
||||
String createFileDeleteCommand(String file);
|
||||
String getSimpleFileWriteCommand(String content, String file);
|
||||
|
||||
String createFileExistsCommand(String file);
|
||||
String getFileDeleteCommand(String file);
|
||||
|
||||
String createFileTouchCommand(String file);
|
||||
String getFileExistsCommand(String file);
|
||||
|
||||
String createWhichCommand(String executable);
|
||||
String getFileTouchCommand(String file);
|
||||
|
||||
String getWhichCommand(String executable);
|
||||
|
||||
Charset determineCharset(ShellProcessControl control) throws Exception;
|
||||
|
||||
|
@ -103,5 +96,5 @@ public interface ShellType {
|
|||
|
||||
String getExecutable();
|
||||
|
||||
boolean echoesInput();
|
||||
boolean doesRepeatInput();
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.Value;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ShellTypes {
|
||||
|
@ -45,6 +45,16 @@ public class ShellTypes {
|
|||
@Value
|
||||
public static class Cmd implements ShellType {
|
||||
|
||||
@Override
|
||||
public String addInlineVariablesToCommand(Map<String, String> variables, String command) {
|
||||
var content = "";
|
||||
for (Map.Entry<String, String> e : variables.entrySet()) {
|
||||
content += ("set \"" + e.getKey() + "=" + e.getValue().replaceAll("\"", "^$0") + "\"");
|
||||
content += getConcatenationOperator();
|
||||
}
|
||||
return content + command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetVariableCommand(String variableName, String value) {
|
||||
return ("set \"" + variableName + "=" + value.replaceAll("\"", "^$0") + "\"");
|
||||
|
@ -66,19 +76,6 @@ public class ShellTypes {
|
|||
+ "\"\r\necho %echov%\r\n@echo on\n(goto) 2>nul & del \"%~f0\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String queryShellProcessId(ShellProcessControl control) throws IOException {
|
||||
control.writeLine("powershell (Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId");
|
||||
|
||||
var r = new BufferedReader(new InputStreamReader(control.getStdout(), StandardCharsets.US_ASCII));
|
||||
// Read echo of command
|
||||
r.readLine();
|
||||
// Read actual output
|
||||
var line = r.readLine();
|
||||
r.readLine();
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConcatenationOperator() {
|
||||
return "&";
|
||||
|
@ -89,11 +86,6 @@ public class ShellTypes {
|
|||
return "echo.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerminalFileOpenCommand(String file) {
|
||||
return String.format("%s %s \"%s\"", getExecutable(), "/C", file);
|
||||
}
|
||||
|
||||
public String escapeStringValue(String input) {
|
||||
return input.replaceAll("[&^|<>\"]", "^$0");
|
||||
}
|
||||
|
@ -109,20 +101,8 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String createInitFileContent(String command) {
|
||||
return "@echo off\n" + command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception {
|
||||
try (CommandProcessControl c = control.command("net session >NUL 2>NUL")) {
|
||||
var exitCode = c.getExitCode();
|
||||
if (exitCode != 0) {
|
||||
throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation.");
|
||||
}
|
||||
}
|
||||
|
||||
control.executeCommand(command);
|
||||
public String prepareScriptContent(String content) {
|
||||
return "@echo off\n" + content;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -151,37 +131,42 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> createMkdirsCommand(String dirs) {
|
||||
public List<String> getMkdirsCommand(String dirs) {
|
||||
return List.of("(", "if", "not", "exist", dirs, "mkdir", dirs, ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileReadCommand(String file) {
|
||||
public String getFileReadCommand(String file) {
|
||||
return "type \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileWriteCommand(String file) {
|
||||
public String getStreamFileWriteCommand(String file) {
|
||||
return "findstr \"^\" > \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileDeleteCommand(String file) {
|
||||
public String getSimpleFileWriteCommand(String content, String file) {
|
||||
return "echo " + content + " > \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileDeleteCommand(String file) {
|
||||
return "rd /s /q \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileExistsCommand(String file) {
|
||||
public String getFileExistsCommand(String file) {
|
||||
return String.format("dir /a \"%s\"", file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileTouchCommand(String file) {
|
||||
public String getFileTouchCommand(String file) {
|
||||
return "COPY NUL \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createWhichCommand(String executable) {
|
||||
public String getWhichCommand(String executable) {
|
||||
return "where \"" + executable + "\"";
|
||||
}
|
||||
|
||||
|
@ -218,7 +203,7 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean echoesInput() {
|
||||
public boolean doesRepeatInput() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +213,22 @@ public class ShellTypes {
|
|||
public static class PowerShell implements ShellType {
|
||||
|
||||
@Override
|
||||
public String createFileTouchCommand(String file) {
|
||||
public String addInlineVariablesToCommand(Map<String, String> variables, String command) {
|
||||
var content = "";
|
||||
for (Map.Entry<String, String> e : variables.entrySet()) {
|
||||
content += "$env:" + e.getKey() + " = \"" + escapeStringValue(e.getValue()) + "\"";
|
||||
content += getConcatenationOperator();
|
||||
}
|
||||
return content + command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSimpleFileWriteCommand(String content, String file) {
|
||||
return "echo \"" + content + "\" | Out-File \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileTouchCommand(String file) {
|
||||
return "$error_count=$error.Count; Out-File -FilePath \"" + file + "\"; $LASTEXITCODE=$error.Count - $error_count";
|
||||
}
|
||||
|
||||
|
@ -257,18 +257,6 @@ public class ShellTypes {
|
|||
return List.of("powershell", "-Command", cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String queryShellProcessId(ShellProcessControl control) throws IOException {
|
||||
control.writeLine("echo $PID");
|
||||
|
||||
var r = new BufferedReader(new InputStreamReader(control.getStdout(), StandardCharsets.US_ASCII));
|
||||
// Read echo of command
|
||||
r.readLine();
|
||||
// Read actual output
|
||||
var line = r.readLine();
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConcatenationOperator() {
|
||||
return ";";
|
||||
|
@ -280,7 +268,7 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean echoesInput() {
|
||||
public boolean doesRepeatInput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -295,33 +283,14 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String createInitFileContent(String command) {
|
||||
return command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerminalFileOpenCommand(String file) {
|
||||
return String.format("%s -ExecutionPolicy Bypass -File \"%s\"", getExecutable(), file);
|
||||
public String prepareScriptContent(String content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String escapeStringValue(String input) {
|
||||
return input.replaceAll("[\"]", "`$0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception {
|
||||
try (CommandProcessControl c = control.command(
|
||||
"([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security" +
|
||||
".Principal.WindowsBuiltinRole]::Administrator)")
|
||||
.start()) {
|
||||
if (c.startAndCheckExit()) {
|
||||
throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation.");
|
||||
}
|
||||
}
|
||||
|
||||
control.executeCommand(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExitCodeVariable() {
|
||||
return "LASTEXITCODE";
|
||||
|
@ -347,32 +316,32 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String createFileReadCommand(String file) {
|
||||
public String getFileReadCommand(String file) {
|
||||
return "cmd /c type \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileWriteCommand(String file) {
|
||||
public String getStreamFileWriteCommand(String file) {
|
||||
return "cmd /c 'findstr \"^\" > \"" + file + "\"'";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> createMkdirsCommand(String dirs) {
|
||||
public List<String> getMkdirsCommand(String dirs) {
|
||||
return List.of("cmd", "/c", "mkdir", dirs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileDeleteCommand(String file) {
|
||||
public String getFileDeleteCommand(String file) {
|
||||
return "rm /path \"" + file + "\" -force";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileExistsCommand(String file) {
|
||||
public String getFileExistsCommand(String file) {
|
||||
return String.format("cmd /c dir /a \"%s\"", file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createWhichCommand(String executable) {
|
||||
public String getWhichCommand(String executable) {
|
||||
return "$LASTEXITCODE=(1 - (Get-Command -erroraction \"silentlycontinue\" \"" + executable + "\").Length)";
|
||||
}
|
||||
|
||||
|
@ -415,7 +384,12 @@ public class ShellTypes {
|
|||
public abstract static class PosixBase implements ShellType {
|
||||
|
||||
@Override
|
||||
public String createFileTouchCommand(String file) {
|
||||
public String getSimpleFileWriteCommand(String content, String file) {
|
||||
return "echo \"" + content + "\" > \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileTouchCommand(String file) {
|
||||
return "touch \"" + file + "\"";
|
||||
}
|
||||
|
||||
|
@ -429,12 +403,12 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String createFileDeleteCommand(String file) {
|
||||
public String getFileDeleteCommand(String file) {
|
||||
return "rm -rf \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createWhichCommand(String executable) {
|
||||
public String getWhichCommand(String executable) {
|
||||
return "which \"" + executable + "\"";
|
||||
}
|
||||
|
||||
|
@ -449,20 +423,25 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String commandWithVariable(String key, String value, String command) {
|
||||
return getSetVariableCommand(key, value) + " " + command;
|
||||
public String addInlineVariablesToCommand(Map<String, String> variables, String command) {
|
||||
var content = "";
|
||||
for (Map.Entry<String, String> e : variables.entrySet()) {
|
||||
content += e.getKey() + "=\"" + e.getValue() + "\"";
|
||||
content += getConcatenationOperator();
|
||||
}
|
||||
return content + command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPauseCommand() {
|
||||
return "bash -c read -rsp \"Press any key to continue...\n\" -n 1 key";
|
||||
return "bash -c read -rsp \"Press any key to continue...\" -n 1 key";
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
@Override
|
||||
public String createInitFileContent(String command) {
|
||||
return command;
|
||||
public String prepareScriptContent(String content) {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -480,23 +459,6 @@ public class ShellTypes {
|
|||
return ";";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerminalFileOpenCommand(String file) {
|
||||
return String.format("%s -i -c \"%s\"", getExecutable(), file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception {
|
||||
if (control.getElevationPassword() == null) {
|
||||
control.executeCommand("SUDO_ASKPASS=/bin/false sudo -n -p \"\" -S -- " + command);
|
||||
return;
|
||||
}
|
||||
|
||||
// For sudo to always query for a password by using the -k switch
|
||||
control.executeCommand("sudo -p \"\" -k -S -- " + command);
|
||||
control.writeLine(control.getElevationPassword().getSecretValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExitCodeVariable() {
|
||||
return "?";
|
||||
|
@ -508,18 +470,8 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String queryShellProcessId(ShellProcessControl control) throws Exception {
|
||||
try (CommandProcessControl c = control.command("echo $$").start()) {
|
||||
var out = c.readOnlyStdout();
|
||||
var matcher = Pattern.compile("\\d+$").matcher(out);
|
||||
matcher.find();
|
||||
return matcher.group(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetVariableCommand(String variableName, String value) {
|
||||
return variableName + "=\"" + value + "\"";
|
||||
public String getSetVariableCommand(String variable, String value) {
|
||||
return "export " + variable + "=\"" + value + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -533,22 +485,22 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> createMkdirsCommand(String dirs) {
|
||||
public List<String> getMkdirsCommand(String dirs) {
|
||||
return List.of("mkdir", "-p", dirs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileReadCommand(String file) {
|
||||
public String getFileReadCommand(String file) {
|
||||
return "cat \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileWriteCommand(String file) {
|
||||
public String getStreamFileWriteCommand(String file) {
|
||||
return "cat > \"" + file + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFileExistsCommand(String file) {
|
||||
public String getFileExistsCommand(String file) {
|
||||
return String.format("test -f \"%s\" || test -d \"%s\"", file, file);
|
||||
}
|
||||
|
||||
|
@ -563,7 +515,7 @@ public class ShellTypes {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean echoesInput() {
|
||||
public boolean doesRepeatInput() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -573,6 +525,11 @@ public class ShellTypes {
|
|||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class Sh extends PosixBase {
|
||||
|
||||
@Override
|
||||
public String getStreamFileWriteCommand(String file) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecutable() {
|
||||
return "/bin/sh";
|
||||
|
|
|
@ -12,21 +12,21 @@ public interface MachineStore extends FileSystemStore, ShellStore {
|
|||
@Override
|
||||
public default InputStream openInput(String file) throws Exception {
|
||||
return create().command(proc -> proc.getShellType()
|
||||
.createFileReadCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.getFileReadCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.startExternalStdout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public default OutputStream openOutput(String file) throws Exception {
|
||||
return create().command(proc -> proc.getShellType()
|
||||
.createFileWriteCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.getStreamFileWriteCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.startExternalStdin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public default boolean exists(String file) throws Exception {
|
||||
try (var pc = create().command(proc -> proc.getShellType()
|
||||
.createFileExistsCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.getFileExistsCommand(proc.getOsType().normalizeFileName(file)))
|
||||
.start()) {
|
||||
return pc.discardAndCheckExit();
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public interface MachineStore extends FileSystemStore, ShellStore {
|
|||
public default boolean mkdirs(String file) throws Exception {
|
||||
try (var pc = create().command(proc -> proc.getShellType()
|
||||
.flatten(proc.getShellType()
|
||||
.createMkdirsCommand(proc.getOsType().normalizeFileName(file))))
|
||||
.getMkdirsCommand(proc.getOsType().normalizeFileName(file))))
|
||||
.start()) {
|
||||
return pc.discardAndCheckExit();
|
||||
}
|
||||
|
|
|
@ -105,6 +105,6 @@ public class Deobfuscator {
|
|||
}
|
||||
|
||||
var t = ShellTypes.getPlatformDefault();
|
||||
return LocalProcess.executeSimpleBooleanCommand(t.createWhichCommand("retrace." + t.getScriptFileEnding()));
|
||||
return LocalProcess.executeSimpleBooleanCommand(t.getWhichCommand("retrace." + t.getScriptFileEnding()));
|
||||
}
|
||||
}
|
||||
|
|
53
core/src/main/java/io/xpipe/core/util/XPipeSession.java
Normal file
53
core/src/main/java/io/xpipe/core/util/XPipeSession.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
package io.xpipe.core.util;
|
||||
|
||||
import io.xpipe.core.process.OsType;
|
||||
import lombok.Value;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.UUID;
|
||||
|
||||
@Value
|
||||
public class XPipeSession {
|
||||
|
||||
boolean isNewSystemSession;
|
||||
|
||||
/**
|
||||
* Unique identifier that resets on every X-Pipe restart.
|
||||
*/
|
||||
UUID sessionId;
|
||||
|
||||
/**
|
||||
* Unique identifier that resets on every X-Pipe update.
|
||||
*/
|
||||
UUID buildSessionId;
|
||||
|
||||
/**
|
||||
* Unique identifier that resets on system restarts.
|
||||
*/
|
||||
UUID systemSessionId;
|
||||
|
||||
private static XPipeSession INSTANCE;
|
||||
|
||||
public static void init(UUID buildSessionId) throws Exception {
|
||||
var sessionFile = XPipeTempDirectory.getLocal().resolve("xpipe_session");
|
||||
var isNew = !Files.exists(sessionFile);
|
||||
var systemSessionId = isNew ? UUID.randomUUID() : UUID.fromString(Files.readString(sessionFile));
|
||||
|
||||
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
var pf = Path.of("C:\\pagefile.sys");
|
||||
BasicFileAttributes attr = Files.readAttributes(pf, BasicFileAttributes.class);
|
||||
var timeUuid = UUID.nameUUIDFromBytes(attr.creationTime().toInstant().toString().getBytes());
|
||||
isNew = isNew && timeUuid.equals(systemSessionId);
|
||||
systemSessionId = timeUuid;
|
||||
}
|
||||
|
||||
Files.writeString(sessionFile, systemSessionId.toString());
|
||||
INSTANCE = new XPipeSession(isNew, UUID.randomUUID(), buildSessionId, systemSessionId);
|
||||
}
|
||||
|
||||
public static XPipeSession get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@ public class XPipeTempDirectory {
|
|||
var base = proc.getOsType().getTempDirectory(proc);
|
||||
var dir = FileNames.join(base, "xpipe");
|
||||
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().createFileExistsCommand(dir))) {
|
||||
proc.executeSimpleCommand(proc.getShellType().flatten(proc.getShellType().createMkdirsCommand(dir)), "Unable to access or create temporary directory " + dir);
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().getFileExistsCommand(dir))) {
|
||||
proc.executeSimpleCommand(proc.getShellType().flatten(proc.getShellType().getMkdirsCommand(dir)), "Unable to access or create temporary directory " + dir);
|
||||
|
||||
if (proc.getOsType().equals(OsType.LINUX) || proc.getOsType().equals(OsType.MAC)) {
|
||||
proc.executeSimpleCommand("(chmod -f 777 \"" + dir + "\"");
|
||||
|
@ -33,7 +33,7 @@ public class XPipeTempDirectory {
|
|||
|
||||
public static void clear(ShellProcessControl proc) throws Exception {
|
||||
var dir = get(proc);
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().createFileDeleteCommand(dir))) {
|
||||
if (!proc.executeBooleanSimpleCommand(proc.getShellType().getFileDeleteCommand(dir))) {
|
||||
throw new IOException("Unable to delete temporary directory " + dir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.io.IOException;
|
|||
public class ApplicationHelper {
|
||||
|
||||
public static boolean isInPath(ShellProcessControl processControl, String executable) throws Exception {
|
||||
return processControl.executeBooleanSimpleCommand(processControl.getShellType().createWhichCommand(executable));
|
||||
return processControl.executeBooleanSimpleCommand(processControl.getShellType().getWhichCommand(executable));
|
||||
}
|
||||
|
||||
public static void checkSupport(ShellProcessControl processControl, String executable, String displayName) throws Exception {
|
||||
|
|
|
@ -4,10 +4,13 @@ import io.xpipe.api.DataSource;
|
|||
import io.xpipe.beacon.BeaconDaemonController;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import io.xpipe.core.util.XPipeSession;
|
||||
import io.xpipe.extension.XPipeServiceProviders;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class DaemonExtensionTest extends ExtensionTest {
|
||||
|
||||
public static DataSource getSource(String type, DataStore store) {
|
||||
|
@ -26,6 +29,7 @@ public class DaemonExtensionTest extends ExtensionTest {
|
|||
public static void setup() throws Exception {
|
||||
JacksonMapper.initModularized(ModuleLayer.boot());
|
||||
XPipeServiceProviders.load(ModuleLayer.boot());
|
||||
XPipeSession.init(UUID.randomUUID());
|
||||
BeaconDaemonController.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellProcessControl;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.XPipeTempDirectory;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ExecScriptHelper {
|
||||
|
||||
public static int getConnectionHash(String command) {
|
||||
return Math.abs(Objects.hash(command));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createLocalExecScript(String content) {
|
||||
try (var l = ShellStore.local().create().start()) {
|
||||
return createExecScript(l, content);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createExecScript(ShellProcessControl processControl, String content) {
|
||||
var fileName = "exec-" + getConnectionHash(content);
|
||||
content = processControl.getShellType().createInitFileContent(content);
|
||||
var temp = XPipeTempDirectory.get(processControl);
|
||||
var file = FileNames.join(
|
||||
temp, fileName + "." + processControl.getShellType().getScriptFileEnding());
|
||||
|
||||
if (processControl.executeBooleanSimpleCommand(processControl.getShellType().createFileExistsCommand(file))) {
|
||||
return file;
|
||||
}
|
||||
|
||||
try (var c = processControl.command(processControl.getShellType()
|
||||
.joinCommands(
|
||||
processControl.getShellType().createFileWriteCommand(file),
|
||||
processControl.getShellType().getMakeExecutableCommand(file)
|
||||
))
|
||||
.start()) {
|
||||
c.discardOut();
|
||||
c.discardErr();
|
||||
c.getStdin().write(content.getBytes(processControl.getCharset()));
|
||||
c.closeStdin();
|
||||
}
|
||||
|
||||
processControl.restart();
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import io.xpipe.core.util.XPipeSession;
|
||||
import io.xpipe.extension.XPipeServiceProviders;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LocalExtensionTest extends ExtensionTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
JacksonMapper.initModularized(ModuleLayer.boot());
|
||||
XPipeServiceProviders.load(ModuleLayer.boot());
|
||||
XPipeSession.init(UUID.randomUUID());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.ShellProcessControl;
|
||||
import io.xpipe.core.process.ShellType;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import io.xpipe.core.util.XPipeSession;
|
||||
import io.xpipe.core.util.XPipeTempDirectory;
|
||||
import io.xpipe.extension.event.TrackEvent;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ScriptHelper {
|
||||
|
||||
public static int getConnectionHash(String command) {
|
||||
return Math.abs(Objects.hash(command, XPipeSession.get().getSystemSessionId()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createLocalExecScript(String content) {
|
||||
try (var l = ShellStore.local().create().start()) {
|
||||
return createExecScript(l, content, false);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createExecScript(ShellProcessControl processControl, String content, boolean restart) {
|
||||
var fileName = "exec-" + getConnectionHash(content);
|
||||
ShellType type = processControl.getShellType();
|
||||
var temp = XPipeTempDirectory.get(processControl);
|
||||
var file = FileNames.join(temp, fileName + "." + type.getScriptFileEnding());
|
||||
return createExecScript(processControl, file, content, restart);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static String createExecScript(ShellProcessControl processControl, String file, String content, boolean restart) {
|
||||
ShellType type = processControl.getShellType();
|
||||
content = type.prepareScriptContent(content);
|
||||
|
||||
if (processControl.executeBooleanSimpleCommand(type.getFileExistsCommand(file))) {
|
||||
return file;
|
||||
}
|
||||
|
||||
TrackEvent.withTrace("proc", "Writing exec script")
|
||||
.tag("file", file)
|
||||
.tag("content", content)
|
||||
.handle();
|
||||
|
||||
processControl.executeSimpleCommand(type.getFileTouchCommand(file), "Failed to create script " + file);
|
||||
processControl.executeSimpleCommand(type.getMakeExecutableCommand(file), "Failed to make script " + file + " executable");
|
||||
|
||||
if (!content.contains("\n")) {
|
||||
processControl.executeSimpleCommand(type.getSimpleFileWriteCommand(content, file));
|
||||
return file;
|
||||
}
|
||||
|
||||
try (var c = processControl.command(type.getStreamFileWriteCommand(file)).start()) {
|
||||
c.discardOut();
|
||||
c.discardErr();
|
||||
c.getStdin().write(content.getBytes(processControl.getCharset()));
|
||||
c.closeStdin();
|
||||
}
|
||||
|
||||
if (restart) {
|
||||
processControl.restart();
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String createAskPassScript(SecretValue pass, ShellProcessControl parent, ShellType type, boolean restart) {
|
||||
var content = type.getScriptEchoCommand(pass.getSecretValue());
|
||||
var temp = XPipeTempDirectory.get(parent);
|
||||
var file = FileNames.join(temp, "askpass-" + getConnectionHash(content) + "." + type.getScriptFileEnding());
|
||||
return createExecScript(parent,file, content, restart);
|
||||
}
|
||||
}
|
|
@ -3,33 +3,34 @@ package io.xpipe.extension.util;
|
|||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.util.ValidationException;
|
||||
import io.xpipe.extension.I18n;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Validators {
|
||||
|
||||
public static void nonNull(Object object, String name) {
|
||||
public static void nonNull(Object object, String name) throws ValidationException {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.null", name));
|
||||
throw new ValidationException(I18n.get("extension.mustNotBeEmpty", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void notEmpty(String string, String name) {
|
||||
public static void notEmpty(String string, String name) throws ValidationException {
|
||||
if (string.trim().length() == 0) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.empty", name));
|
||||
throw new ValidationException(I18n.get("extension.mustNotBeEmpty", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void namedStoreExists(DataStore store, String name) {
|
||||
public static void namedStoreExists(DataStore store, String name) throws ValidationException {
|
||||
if (!XPipeDaemon.getInstance().getNamedStores().contains(store) && !(store instanceof LocalStore)) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.missingStore", name));
|
||||
throw new ValidationException(I18n.get("extension.missingStore", name));
|
||||
}
|
||||
}
|
||||
|
||||
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) {
|
||||
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) throws ValidationException {
|
||||
if (!predicate.test(host)) {
|
||||
throw new IllegalArgumentException(I18n.get("extension.hostFeatureUnsupported", name));
|
||||
throw new ValidationException(I18n.get("extension.hostFeatureUnsupported", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ lf=LF (Linux)
|
|||
none=None
|
||||
common=Common
|
||||
other=Other
|
||||
nullPointer=Null Pointer: $MSG$
|
||||
nullPointer=Null Pointer
|
||||
mustNotBeEmpty=$NAME$ must not be empty
|
||||
null=$VALUE$ must be not null
|
||||
hostFeatureUnsupported=Host does not support the feature $FEATURE$
|
||||
|
|
Loading…
Reference in a new issue