Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support to send back or store RUN_COMMAND intent command results …
…in files and provide way to fix argument splitting sent with am command ### `RUN_COMMAND` Results in Files Previously in `v0.109` with a2209dd support was added in RUN_COMMAND intent to send back foreground and background command results with `PendingIntent` to the intent sender. However, this was only usable with java code by android apps. But if you were sending the intent with the `am` command from inside a shell, like tasker `Run Shell` action, you could not get the result back directly. You could technically manually save the output of your script in files under `/sdcard` with redirection and wait for them to be created in the `Run Shell` so that you could process the result. However, this was only possible for background commands and the caller would hang indefinitely if a termux internal `errmsg` was generated like it does for termux-tasker, likely caused by incorrect intent extra arguments, an exception being raised when executing the executable/script, or termux being closed with the exit button, etc. Now native support has been added inside termux to store results of both foreground and background commands inside files, that also sends back internal `errmsgs` as long as result files extras are valid. This can be used to run synchronous commands from inside termux, with other apps that have `Run commands in Termux environment` (`com.termux.permission.RUN_COMMAND`) like Tasker, from pc over `adb` or inside `adb shell` if you have a rooted device, or from pc if you have setup termux `sshd`. The `RUN_COMMAND` intent can only be sent by the `termux` user itself, by an app that has the permission or by the `root` user. The `shell` user of `adb` cannot send it. A script will be provided at a later time that will automatically detect these cases to easily run `RUN_COMMAND` intent commands which will also automatically create temp directories and do cleanup. This can also be useful inside termux itself, like if you want to start a new foreground session and to automatically store its output to a log file when you exit. Support can also be added for this to be done for termux-boot and termux-widget as well but will require updates for them. There is obviously a security and privacy concern for this if you use shared storage `/sdcard` to store the result files since malicious apps could read them and optionally modify them for MITM attacks if you are reading the result and processing it unsafely. But users access other files from shared storage anyways for other scripts. Saving the result files on shared storage would only be necessary if you want to read the result back, like in Tasker or over adb since non-termux and non-root users can't access termux private app data directory `/data/data/com.termux`. For internal termux usage, this shouldn't be a concern if files are saved inside termux private app data directory. The extra constant values are defined by [`TermuxConstants`](https://github.com/termux/termux-app/tree/master/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java) class of the [`termux-shared`](https://github.com/termux/termux-app/tree/master/termux-shared) library. The [`ResultSender`](https://github.com/termux/termux-app/tree/master/termux-shared/src/main/java/com/termux/shared/shell/ResultSender.java) class actually sends back the results. The following extras have been added: - The `String` `RUN_COMMAND_SERVICE.EXTRA_RESULT_DIRECTORY` extra for the directory path in which to write the result of the execution command for the execute command caller. - The `boolean` `RUN_COMMAND_SERVICE.EXTRA_RESULT_SINGLE_FILE` extra for whether the result should be written to a single file or multiple files (`err`, `errmsg`, `stdout`, `stderr`, `exit_code`) in `EXTRA_RESULT_DIRECTORY`. - The `String` `RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_BASENAME` extra for the basename of the result file that should be created in `EXTRA_RESULT_DIRECTORY` if `EXTRA_RESULT_SINGLE_FILE` is `true`. - The `String` `RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_OUTPUT_FORMAT` extra for the output [`Formatter`](https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html) format of the `EXTRA_RESULT_FILE_BASENAME` result file. - The `String` `RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_ERROR_FORMAT` extra for the error [`Formatter`](https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html) format of the `EXTRA_RESULT_FILE_BASENAME` result file. - The `String` `RUN_COMMAND_SERVICE.EXTRA_RESULT_FILES_SUFFIX` extra for the optional suffix of the result files that should be created in `EXTRA_RESULT_DIRECTORY` if `EXTRA_RESULT_SINGLE_FILE` is `false`. The `err` and `errmsg` are for internal termux errors like invalid intent extras, etc and not related to the shell commands itself. This is the same way Tasker actions and plugins system work with [`%err` and `%errmsg`](https://tasker.joaoapps.com/userguide/en/variables.html#localbuiltin). The `err` will be equal to `Errno.ERRNO_SUCCESS` (`-1`) if no internal errors are set. The `stdout`, `stderr` and `exit_code` are for the shell commands. The `exit_code` is normally `0` for success. There are two modes for getting back the result in results files. ##### `EXTRA_RESULT_SINGLE_FILE` extra is `true` Only a single file will be created under `EXTRA_RESULT_DIRECTORY` that will contain the `err`, `errmsg`, `stdout`, `stderr` and `exit_code` in a specific format defined by `RESULT_SENDER.FORMAT_*` constants in `TermuxConstants` class depending on the exit status of the command. By default if the `EXTRA_RESULT_FILE_BASENAME` extra is not passed, the basename of the result file will be set to `<command_path_basename>-<timestamp>.log` where `<timestamp>` will be in the `yyyy-MM-dd_HH.mm.ss.SSS` format. The `EXTRA_RESULT_FILE_OUTPUT_FORMAT` extra can be passed with a custom format that should be used when `err` equals `-1` and `EXTRA_RESULT_FILE_ERROR_FORMAT` extra for when its greater than `-1`. The value `0` is for `Errno.ERRNO_CANCELLED` and should also be considered a failure unlike `exit_code`. ``` am startservice --user 0 -n 'com.termux/com.termux.app.RunCommandService' -a 'com.termux.RUN_COMMAND' --es 'com.termux.RUN_COMMAND_PATH' '$PREFIX/bin/top' --esa 'com.termux.RUN_COMMAND_ARGUMENTS' '-n,5' --ez 'com.termux.RUN_COMMAND_BACKGROUND' '0' --es 'com.termux.RUN_COMMAND_RESULT_DIRECTORY' '/sdcard/.termux-app' --ez 'com.termux.RUN_COMMAND_RESULT_SINGLE_FILE' 'true' --es 'com.termux.RUN_COMMAND_RESULT_FILE_BASENAME' 'top.log' ``` ##### `EXTRA_RESULT_SINGLE_FILE` extra is `false` Separate files will be created under `EXTRA_RESULT_DIRECTORY` for each of the `err`, `errmsg`, `stdout`, `stderr` and `exit_code`. Their basenames (same as mentioned) are defined by the `RESULT_FILE_*` constants in `TermuxConstants` class. If the `EXTRA_RESULT_FILES_SUFFIX` extra is passed, then that will be suffixed to the basename of each file like `err<suffix>`, `stdout<suffix>`, etc. The `err` file will be created after writing to other result files has already finished and this is the file the caller should optionally wait for to be created to be notified that the command has finished, like with `test -f "$result_directory/err"` command in an infinite loop (with sleep+timeout) or with `inotify`. After it has been read, caller can start reading from the rest of the result files if they exist. The `errmsg`, `stdout`, `stderr` and `exit_code` files will not be created if nothing is to be written to them, so no do wait for these files. If you are not passing a unique suffix for each intent, then result files of multiple simultaneous intent commands will conflict with each other. So ideally a temp directory should be created for each intent command and that should be passed as `EXTRA_RESULT_DIRECTORY`. You can use `mktemp` command to create a unique name and create the directory for you. ``` temp_directory="$(/system/bin/mktemp -d --tmpdir="/sdcard/.termux-app" "top.XXXXXX")" || return $? am startservice --user 0 -n 'com.termux/com.termux.app.RunCommandService' -a 'com.termux.RUN_COMMAND' --es 'com.termux.RUN_COMMAND_PATH' '$PREFIX/bin/top' --esa 'com.termux.RUN_COMMAND_ARGUMENTS' '-n,5' --ez 'com.termux.RUN_COMMAND_BACKGROUND' '1' --es 'com.termux.RUN_COMMAND_RESULT_DIRECTORY' "$temp_directory" --ez 'com.termux.RUN_COMMAND_RESULT_SINGLE_FILE' 'false' ``` Use following if in termux and not in tasker/rooted shell. ``` temp_directory="$(PATH=/system/bin; LD_LIBRARY_PATH=/system/lib64:/system/lib; unset LD_PRELOAD; mktemp -d --tmpdir="/sdcard/.termux-app" "top.XXXXXX")" || return $? ``` Note that since there may be a delay between creation of `result_file`/`err` file and writing to it or flushing to disk, a temp file is created first suffixed with `-<timestamp>` which is then moved to the final destination, since caller may otherwise read from an empty file in some cases otherwise. Commands will automatically be killed and result up till that point returned if user exits termux app like with the `Exit` button in the notification. The exit code will be `137` (`SIGKILL`). -------------------- ### `RUN_COMMAND` Arguments Splitting with `am` Command If `am` command is used to send the `RUN_COMMAND` intent and you want to pass an argument with the `--esa com.termux.RUN_COMMAND_ARGUMENTS` string array extra that itself contains a normal comma `,` (`U+002C`, `,`, `,`, `comma`), it must be escaped with a backslash `\,` so that the argument isn't split into multiple arguments. The only problem is that, the arguments received by the termux will contain `\,` instead of `,` since the reversal isn't done as described in the [am command source](https://android.googlesource.com/platform/frameworks/base/+/21bdaf1/cmds/am/src/com/android/commands/am/Am.java#572) while converting to a string array. There is also no way for the `am` command or termux to know whether `\,` was done to prevent arguments splitting or `\,` was a literal string naturally part of the argument. ``` // Split on commas unless they are preceeded by an escape. // The escape character must be escaped for the string and // again for the regex, thus four escape characters become one. intent.putExtra(key, strings); ``` To fix this termux now supports an alternative method to handle such conditions. If an argument contains a normal comma `,`, then instead of escaping them with a backslash `\,`, replace all normal commas with the comma alternate character `‚` (`#U+201A`, `‚`, `‚`, `single low-9 quotation mark`) before sending the intent with the `am` command. This way argument splitting will not be done. You can pass the `com.termux.RUN_COMMAND_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS` `boolean` extra in the `RUN_COMMAND` intent so that termux replaces all the comma alternate characters back to normal commas. It would be unlikely for the the arguments to naturally contain the comma alternate characters for this to be a problem. Even if they do, they might not be significant for any logic. If they are, then you can set a different character that should be replaced, by passing it in the `com.termux.RUN_COMMAND_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS` `String` extra. If `tudo` or `sudo` are used, then simply using their `-r` and `--comma-alternative` command options can be used without passing the below extras, but native supports is helpful if they are not being used. https://github.com/agnostic-apollo/tudo#passing-arguments-using-run_command-intent The following extras have been added: - The `boolean` `RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS` extra for whether to replace comma alternative characters in arguments with normal comma `,` (`U+002C`, `,`, `,`, `comma`). - The `String` `RUN_COMMAND_SERVICE.EXTRA_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS` extra for the comma alternative characters in arguments that should be replaced instead of the default comma alternate character `‚` (`#U+201A`, `‚`, `‚`, `single low-9 quotation mark`). ``` am startservice --user 0 -n 'com.termux/com.termux.app.RunCommandService' -a 'com.termux.RUN_COMMAND' --es 'com.termux.RUN_COMMAND_PATH' '$PREFIX/bin/bash' --esa 'com.termux.RUN_COMMAND_ARGUMENTS' '-c,echo "Argument with commas here _ and here _ that have been converted to an underscore before sending"; sleep 5' --ez 'com.termux.RUN_COMMAND_BACKGROUND' '0' --ez 'com.termux.RUN_COMMAND_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS' 'true' --es 'com.termux.RUN_COMMAND_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS' '_' ``` Note that since `0.109`, the `RUN_COMMAND` intent supports `RUN_COMMAND_SERVICE.EXTRA_STDIN`, so instead of passing arguments, just pass a script as `stdin` to the `bash` executable so that you don't have to deal with this "mess". You will have to surround the script with single quotes and escape any single quotes inside the script itself, like each single quote `'` with `'\''`. -------------------- ### Internal Changes This commit also adds onto 679e0de and 4494bc6 The `ExecutionCommand` has been updated and command result variables have been moved to `ResultData` and result configuration to `ResultConfig` since the later two should be agnostic of what type of command there are for. They don't necessarily have to be for terminal/shell commands and can be used for plugin APIs, etc. The `ResultData` instead of a `String` `errmsg` now stores a list of `Error` objects. This is necessary since multiple errors may be picked up while a command is run, like say working directory is invalid and an error is returned by FileUtils and while sending the result to the caller, the `ResultSender` returns an additional error because result configuration like result directory or result output format was invalid. In these situations `PluginUtils` will show a notification to the user with info of each error thrown. In addition to above, in `ResultData`, the `stdout` and `stderr` are converted to `StringBuilder` instead of a `String`. This allows for data to be appended to each from various places in code like log debug or error entries for API commands without having to create a new `String` object each time value needs to updated. This can be useful so that the caller doesn't have to check `logcat` for API commands. This does not apply to `ExecutionCommand` since only `TermuxSession` and `TermuxTask` set the data. The `ResultSender` class is what handles the result of commands whether they need to be sent via `PendingIntent` or to a result directory based on the `ResultConfig` object passed. Result will be sent through both if both of them are not `null`. The `TermuxConstants` class has been updated to `v0.24.0`. Check its Changelog section for info on changes.
- Loading branch information