diff --git a/Makefile.am b/Makefile.am
index f0f4350..964dedf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,7 +16,7 @@
# along with termux-tools. If not, see
# .
-SUBDIRS = . scripts doc mirrors motds
+SUBDIRS = . scripts doc mirrors motds src
CONFFILES = \
etc/motd \
diff --git a/configure.ac b/configure.ac
index c50690d..1770d84 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,6 +27,8 @@ AC_INIT([termux-tools], [1.43.3], [support@termux.dev])
AM_INIT_AUTOMAKE([foreign])
AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_LANG(C)
copyright="Copyright (C) 2022-2024 Termux."
if test "${TERMUX_APP_PACKAGE+set}" = set; then
@@ -93,6 +95,6 @@ AC_SUBST(termux_package_manager)
AC_PROG_LN_S
AC_CONFIG_FILES([Makefile scripts/Makefile doc/Makefile
-mirrors/Makefile motds/Makefile])
+mirrors/Makefile motds/Makefile src/Makefile])
AC_OUTPUT
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index 9dca981..34d0fde 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -24,7 +24,7 @@ termux-setup-package-manager termux-setup-storage termux-wake-lock \
termux-wake-unlock
# wrappers around tools in /system/bin:
-bin_SCRIPTS += cmd df getprop logcat ping ping6 pm settings top
+bin_SCRIPTS += df getprop logcat ping ping6 pm settings top
CLEANFILES = $(bin_SCRIPTS)
@@ -90,4 +90,3 @@ $(eval $(call wrapper-rule,pm))
$(eval $(call wrapper-rule,settings))
$(eval $(call wrapper-rule,top))
$(eval $(call wrapper-rule,umount))
-$(eval $(call wrapper-rule,cmd))
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..d2b3d76
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,23 @@
+# Copyright (C) 2024 Termux
+
+# This file is part of termux-tools.
+
+# termux-tools is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# termux-tools is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with termux-tools. If not, see
+# .
+
+AM_CFLAGS = -Wall -Wextra -pedantic
+
+bin_PROGRAMS = cmd
+
+cmd_SOURCES = cmd.c
diff --git a/src/cmd.c b/src/cmd.c
new file mode 100644
index 0000000..c2c6af5
--- /dev/null
+++ b/src/cmd.c
@@ -0,0 +1,112 @@
+/* cmd.c
+Copyright (C) 2024 5ec1cff
+This file is part of termux-tools.
+termux-tools is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+termux-tools is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+You should have received a copy of the GNU General Public License
+along with termux-tools. If not, see
+. */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+void pump(int in_fd, int out_fd) {
+ char buf[4096];
+ ssize_t sz, t;
+ for (;;) {
+ sz = TEMP_FAILURE_RETRY(read(in_fd, buf, sizeof(buf)));
+ if (sz <= 0) return;
+ while (sz) {
+ t = TEMP_FAILURE_RETRY(write(out_fd, buf, sz));
+ if (t <= 0) return;
+ sz -= t;
+ }
+ }
+}
+
+int p_std_in[2], p_std_out[2], p_std_err[2];
+
+void *pump_stdin(void *ignore) {
+ pump(STDIN_FILENO, p_std_in[1]);
+ close(p_std_in[1]);
+ return NULL;
+}
+
+void *pump_stdout(void *ignore) {
+ pump(p_std_out[0], STDOUT_FILENO);
+ close(p_std_out[0]);
+ return NULL;
+}
+
+void *pump_stderr(void *ignore) {
+ pump(p_std_err[0], STDERR_FILENO);
+ close(p_std_err[0]);
+ return NULL;
+}
+
+void replace_fd(int fd, int target_fd) {
+ if (dup2(fd, target_fd) == -1) err(EXIT_FAILURE, "dup");
+ close(fd);
+ if (fcntl(target_fd, F_SETFD, fcntl(target_fd, F_GETFD) & ~FD_CLOEXEC) == -1)
+ err(EXIT_FAILURE, "replace_fd");
+}
+
+int main(int argc, char **argv) {
+ if (pipe(p_std_in) == -1) err(EXIT_FAILURE, "pipe");
+ if (pipe(p_std_out) == -1) err(EXIT_FAILURE, "pipe");
+ if (pipe(p_std_err) == -1) err(EXIT_FAILURE, "pipe");
+
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ err(EXIT_FAILURE, "fork");
+ } else if (pid > 0) {
+ close(p_std_in[0]);
+ close(p_std_out[1]);
+ close(p_std_err[1]);
+
+ signal(SIGPIPE, SIG_IGN);
+
+ pthread_t t_stdin;
+ pthread_create(&t_stdin, NULL, pump_stdin, NULL);
+ pthread_detach(t_stdin);
+
+ pthread_t t_stdout;
+ pthread_create(&t_stdout, NULL, pump_stdout, NULL);
+
+ pthread_t t_stderr;
+ pthread_create(&t_stderr, NULL, pump_stderr, NULL);
+
+ int status;
+ if (TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)) < 0) err(EXIT_FAILURE, "wait");
+
+ pthread_join(t_stdout, NULL);
+ pthread_join(t_stderr, NULL);
+
+ if (WIFEXITED(status))
+ exit(WEXITSTATUS(status));
+ else
+ exit(EXIT_FAILURE);
+ } else {
+ replace_fd(p_std_in[0], STDIN_FILENO);
+ replace_fd(p_std_out[1], STDOUT_FILENO);
+ replace_fd(p_std_err[1], STDERR_FILENO);
+
+ execv("/system/bin/cmd", argv);
+ err(EXIT_FAILURE, "exec");
+ }
+}