Skip to content

Commit 192b208

Browse files
Add support for stdin for background TermuxTasks
This will allow passing scripts (to bash or python) or other data to an executable via stdin. Arguments are passed to the executable and not the script.
1 parent 824b3e6 commit 192b208

File tree

5 files changed

+70
-18
lines changed

5 files changed

+70
-18
lines changed

app/src/main/java/com/termux/app/TermuxService.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,8 @@ private void executeTermuxTaskCommand(ExecutionCommand executionCommand) {
392392

393393
/** Create a {@link TermuxTask}. */
394394
@Nullable
395-
public TermuxTask createTermuxTask(String executablePath, String[] arguments, String workingDirectory) {
396-
return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, true, false));
395+
public TermuxTask createTermuxTask(String executablePath, String[] arguments, String stdin, String workingDirectory) {
396+
return createTermuxTask(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, stdin, workingDirectory, true, false));
397397
}
398398

399399
/** Create a {@link TermuxTask}. */
@@ -479,8 +479,8 @@ private void executeTermuxSessionCommand(ExecutionCommand executionCommand) {
479479
* Currently called by {@link TermuxTerminalSessionClient#addNewSession(boolean, String)} to add a new {@link TermuxSession}.
480480
*/
481481
@Nullable
482-
public TermuxSession createTermuxSession(String executablePath, String[] arguments, String workingDirectory, boolean isFailSafe, String sessionName) {
483-
return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, workingDirectory, false, isFailSafe), sessionName);
482+
public TermuxSession createTermuxSession(String executablePath, String[] arguments, String stdin, String workingDirectory, boolean isFailSafe, String sessionName) {
483+
return createTermuxSession(new ExecutionCommand(getNextExecutionId(), executablePath, arguments, stdin, workingDirectory, false, isFailSafe), sessionName);
484484
}
485485

486486
/** Create a {@link TermuxSession}. */

app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public void addNewSession(boolean isFailSafe, String sessionName) {
206206
workingDirectory = currentSession.getCwd();
207207
}
208208

209-
TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, workingDirectory, isFailSafe, sessionName);
209+
TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, null, workingDirectory, isFailSafe, sessionName);
210210
if (newTermuxSession == null) return;
211211

212212
TerminalSession newTerminalSession = newTermuxSession.getTerminalSession();

termux-shared/src/main/java/com/termux/shared/models/ExecutionCommand.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public int getValue() {
7474
public Uri executableUri;
7575
/** The executable arguments array for the {@link ExecutionCommand}. */
7676
public String[] arguments;
77+
/** The stdin string for the {@link ExecutionCommand}. */
78+
public String stdin;
7779
/** The current working directory for the {@link ExecutionCommand}. */
7880
public String workingDirectory;
7981

@@ -138,10 +140,11 @@ public ExecutionCommand(Integer id) {
138140
this.id = id;
139141
}
140142

141-
public ExecutionCommand(Integer id, String executable, String[] arguments, String workingDirectory, boolean inBackground, boolean isFailsafe) {
143+
public ExecutionCommand(Integer id, String executable, String[] arguments, String stdin, String workingDirectory, boolean inBackground, boolean isFailsafe) {
142144
this.id = id;
143145
this.executable = executable;
144146
this.arguments = arguments;
147+
this.stdin = stdin;
145148
this.workingDirectory = workingDirectory;
146149
this.inBackground = inBackground;
147150
this.isFailsafe = isFailsafe;
@@ -560,4 +563,8 @@ public synchronized boolean isExecuting() {
560563
return currentState == ExecutionState.EXECUTING;
561564
}
562565

566+
public synchronized boolean isSuccessful() {
567+
return currentState == ExecutionState.SUCCESS;
568+
}
569+
563570
}

termux-shared/src/main/java/com/termux/shared/shell/TermuxTask.java

+55-12
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import com.termux.shared.logger.Logger;
1414
import com.termux.shared.models.ExecutionCommand.ExecutionState;
1515

16+
import java.io.DataOutputStream;
1617
import java.io.File;
1718
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
1820

1921
/**
2022
* A class that maintains info for background Termux tasks run with {@link Runtime#exec(String[], String[], File)}.
@@ -92,7 +94,7 @@ public static TermuxTask execute(@NonNull final Context context, @NonNull Execut
9294

9395
if (isSynchronous) {
9496
try {
95-
termuxTask.executeInner();
97+
termuxTask.executeInner(context);
9698
} catch (IllegalThreadStateException | InterruptedException e) {
9799
// TODO: Should either of these be handled or returned?
98100
}
@@ -101,7 +103,7 @@ public static TermuxTask execute(@NonNull final Context context, @NonNull Execut
101103
@Override
102104
public void run() {
103105
try {
104-
termuxTask.executeInner();
106+
termuxTask.executeInner(context);
105107
} catch (IllegalThreadStateException | InterruptedException e) {
106108
// TODO: Should either of these be handled or returned?
107109
}
@@ -118,8 +120,10 @@ public void run() {
118120
* If the processes finishes, then sets {@link ExecutionCommand#stdout}, {@link ExecutionCommand#stderr}
119121
* and {@link ExecutionCommand#exitCode} for the {@link #mExecutionCommand} of the {@code termuxTask}
120122
* and then calls {@link #processTermuxTaskResult(TermuxTask, ExecutionCommand) to process the result}.
123+
*
124+
* @param context The {@link Context} for operations.
121125
*/
122-
private void executeInner() throws IllegalThreadStateException, InterruptedException {
126+
private void executeInner(@NonNull final Context context) throws IllegalThreadStateException, InterruptedException {
123127
final int pid = ShellUtils.getPid(mProcess);
124128

125129
Logger.logDebug(LOG_TAG, "Running \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid);
@@ -129,15 +133,42 @@ private void executeInner() throws IllegalThreadStateException, InterruptedExcep
129133
mExecutionCommand.exitCode = null;
130134

131135

132-
133-
// setup stdout and stderr gobblers
136+
// setup stdin, and stdout and stderr gobblers
137+
DataOutputStream STDIN = new DataOutputStream(mProcess.getOutputStream());
134138
StreamGobbler STDOUT = new StreamGobbler(pid + "-stdout", mProcess.getInputStream(), mStdout);
135139
StreamGobbler STDERR = new StreamGobbler(pid + "-stderr", mProcess.getErrorStream(), mStderr);
136140

137141
// start gobbling
138142
STDOUT.start();
139143
STDERR.start();
140144

145+
if (mExecutionCommand.stdin != null && !mExecutionCommand.stdin.isEmpty()) {
146+
try {
147+
STDIN.write((mExecutionCommand.stdin + "\n").getBytes(StandardCharsets.UTF_8));
148+
STDIN.flush();
149+
STDIN.close();
150+
//STDIN.write("exit\n".getBytes(StandardCharsets.UTF_8));
151+
//STDIN.flush();
152+
} catch(IOException e){
153+
if (e.getMessage().contains("EPIPE") || e.getMessage().contains("Stream closed")) {
154+
// Method most horrid to catch broken pipe, in which case we
155+
// do nothing. The command is not a shell, the shell closed
156+
// STDIN, the script already contained the exit command, etc.
157+
// these cases we want the output instead of returning null.
158+
} else {
159+
// other issues we don't know how to handle, leads to
160+
// returning null
161+
mExecutionCommand.setStateFailed(ExecutionCommand.RESULT_CODE_FAILED, context.getString(R.string.error_exception_received_while_executing_termux_task_command, mExecutionCommand.getCommandIdAndLabelLogString(), e.getMessage()), e);
162+
mExecutionCommand.stdout = mStdout.toString();
163+
mExecutionCommand.stderr = mStderr.toString();
164+
mExecutionCommand.exitCode = -1;
165+
TermuxTask.processTermuxTaskResult(this, null);
166+
kill();
167+
return;
168+
}
169+
}
170+
}
171+
141172
// wait for our process to finish, while we gobble away in the background
142173
int exitCode = mProcess.waitFor();
143174

@@ -146,6 +177,11 @@ private void executeInner() throws IllegalThreadStateException, InterruptedExcep
146177
// needed in theory, and may even produce warnings, in "normal" Java
147178
// they are required for guaranteed cleanup of resources, so lets be
148179
// safe and do this on Android as well
180+
try {
181+
STDIN.close();
182+
} catch (IOException e) {
183+
// might be closed already
184+
}
149185
STDOUT.join();
150186
STDERR.join();
151187
mProcess.destroy();
@@ -201,13 +237,20 @@ public void killIfExecuting(@NonNull final Context context, boolean processResul
201237
}
202238

203239
if (mExecutionCommand.isExecuting()) {
204-
int pid = ShellUtils.getPid(mProcess);
205-
try {
206-
// Send SIGKILL to process
207-
Os.kill(pid, OsConstants.SIGKILL);
208-
} catch (ErrnoException e) {
209-
Logger.logWarn(LOG_TAG, "Failed to send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + ": " + e.getMessage());
210-
}
240+
kill();
241+
}
242+
}
243+
244+
/**
245+
* Kill this {@link TermuxTask} by sending a {@link OsConstants#SIGILL} to its {@link #mProcess}.
246+
*/
247+
public void kill() {
248+
int pid = ShellUtils.getPid(mProcess);
249+
try {
250+
// Send SIGKILL to process
251+
Os.kill(pid, OsConstants.SIGKILL);
252+
} catch (ErrnoException e) {
253+
Logger.logWarn(LOG_TAG, "Failed to send SIGKILL to \"" + mExecutionCommand.getCommandIdAndLabelLogString() + "\" TermuxTask with pid " + pid + ": " + e.getMessage());
211254
}
212255
}
213256

termux-shared/src/main/res/values/strings.xml

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
<string name="error_execution_cancelled">Execution has been cancelled since execution service is being killed</string>
8383
<string name="error_failed_to_execute_termux_session_command">"Failed to execute \"%1$s\" termux session command"</string>
8484
<string name="error_failed_to_execute_termux_task_command">"Failed to execute \"%1$s\" termux task command"</string>
85+
<string name="error_exception_received_while_executing_termux_session_command">Exception received while to executing \"%1$s\" termux session command.\nException: %2$s</string>
86+
<string name="error_exception_received_while_executing_termux_task_command">Exception received while to executing \"%1$s\" termux task command.\nException: %2$s"</string>
8587

8688

8789

0 commit comments

Comments
 (0)