From f7cc64d72f393ad598ef81b2633d1ad773380859 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 18 May 2026 16:10:06 +0800 Subject: [PATCH 1/6] New ProgramLauncher module (APIs for launching external programs) This module introduces function interfaces for checking permissions of external programs and launching them with dropped privileges whenever it is appliable. These functions are intended to replace the naive execlp(3) and execvp(3) calls as the latter can launch potentially malicious programs without checking, when given root privileges. This module contains these APIs: * ProgramLauncher_setPath() * ProgramLauncher_execve() Features: * It can drop set-UID (SUID) privileges before launching a program. * It checks the owner ID, group ID, and the permission (mode) bits of the program. The policy of executing is stricter than what the OS would permit execution. In particular it respects the "other execute" (o+x) bit only if the program is owned by the root user. * It uses fexecve(3) rather than execve(2) whenever it is supported by the OS. fexecve() is safe against data race. (Unfortunately macOS and OpenBSD don't yet support fexecve() at the time of writing.) * It can search the program through PATH variable and cache the result. It caches the inode as well as the full program path. If the program executable is quietly swapped during htop runtime, htop will refuse to launch the program until the htop instance ends. Signed-off-by: Kang-Che Sung --- Makefile.am | 2 + ProgramLauncher.c | 405 ++++++++++++++++++++++++++++++++++++++++++++++ ProgramLauncher.h | 77 +++++++++ configure.ac | 2 + 4 files changed, 486 insertions(+) create mode 100644 ProgramLauncher.c create mode 100644 ProgramLauncher.h diff --git a/Makefile.am b/Makefile.am index 52134df64..e175dc8d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,6 +74,7 @@ myhtopsources = \ Process.c \ ProcessLocksScreen.c \ ProcessTable.c \ + ProgramLauncher.c \ Row.c \ RichString.c \ Scheduling.c \ @@ -144,6 +145,7 @@ myhtopheaders = \ Process.h \ ProcessLocksScreen.h \ ProcessTable.h \ + ProgramLauncher.h \ ProvideCurses.h \ ProvideTerm.h \ RichString.h \ diff --git a/ProgramLauncher.c b/ProgramLauncher.c new file mode 100644 index 000000000..b9c6d997d --- /dev/null +++ b/ProgramLauncher.c @@ -0,0 +1,405 @@ +/* +htop - ProgramLauncher.c +(C) 2026 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "ProgramLauncher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "XUtils.h" + + +typedef struct ProgramFileRef_ { + dev_t dev; + ino_t ino; + char* path; +} ProgramFileRef; + +extern char** environ; + +static gid_t ProgramLauncher_savedSetGid = (gid_t)-1; +static uid_t ProgramLauncher_savedSetUid = (uid_t)-1; + +void ProgramLauncher_done(ProgramLauncher* this) { + if (this->fileRef) { + free(((const ProgramFileRef*)this->fileRef)->path); + } + free(this->fileRef); + this->fileRef = NULL; + this->lastErrno = 0; +} + +static void ProgramLauncher_dropSetUid(void) { + gid_t egid = getegid(); + if (ProgramLauncher_savedSetGid == (gid_t)-1) + ProgramLauncher_savedSetGid = egid; + + assert(ProgramLauncher_savedSetGid == egid); + + uid_t euid = geteuid(); + if (ProgramLauncher_savedSetUid == (uid_t)-1) + ProgramLauncher_savedSetUid = euid; + + assert(ProgramLauncher_savedSetUid == euid); + + int res = 0; + + gid_t rgid = getgid(); +#ifdef _POSIX_SAVED_IDS + if (egid != rgid) + res = setegid(rgid); +#else + // See Glibc manual about the real UID and effective UID swap: + // https://sourceware.org/glibc/manual/latest/html_node/Enable_002fDisable-Setuid.html + if (ProgramLauncher_savedSetGid != rgid) + res = setregid(egid, rgid); +#endif + if (res != 0) + fail(); + + uid_t ruid = getuid(); +#ifdef _POSIX_SAVED_IDS + if (euid != ruid) + res = seteuid(ruid); +#else + if (ProgramLauncher_savedSetUid != ruid) + res = setreuid(euid, ruid); +#endif + if (res != 0) + fail(); +} + +static void ProgramLauncher_restoreSetUid(void) { + assert(ProgramLauncher_savedSetGid != (gid_t)-1); + assert(ProgramLauncher_savedSetUid != (uid_t)-1); + if (ProgramLauncher_savedSetGid == (gid_t)-1 || ProgramLauncher_savedSetUid == (uid_t)-1) { + fail(); + } + + gid_t egid = getegid(); + if (egid != ProgramLauncher_savedSetGid) { +#ifdef _POSIX_SAVED_IDS + int res = setegid(ProgramLauncher_savedSetGid); +#else + int res = setregid(egid, ProgramLauncher_savedSetGid); +#endif + if (res != 0) + fail(); + } + + uid_t euid = geteuid(); + if (euid != ProgramLauncher_savedSetUid) { +#ifdef _POSIX_SAVED_IDS + int res = seteuid(ProgramLauncher_savedSetUid); +#else + int res = setreuid(euid, ProgramLauncher_savedSetUid); +#endif + if (res != 0) + fail(); + } +} + +#ifndef HAVE_GROUP_MEMBER +static int group_member(gid_t gid) { + int n = getgroups(0, NULL); + if (n <= 0) + return 0; + + gid_t* groups = xMallocArray((size_t)n, sizeof(*groups)); + + int found = 0; + if (getgroups(n, groups) >= 0) { + for (size_t i = 0; i < (size_t)n; i++) { + if (groups[i] == gid) { + found = 1; + break; + } + } + } + free(groups); + return found; +} +#endif + +static bool ProgramLauncher_canTrustExecStat(const struct stat* sb) { + if (sb->st_uid == geteuid()) + return S_ISREG(sb->st_mode) && (sb->st_mode & S_IXUSR); + if (sb->st_gid == getegid() || group_member(sb->st_gid)) + return S_ISREG(sb->st_mode) && (sb->st_mode & S_IXGRP); + + // To prevent users from executing programs they might not trust, + // ignore S_IXOTH except for programs owned by the root user. + // This is stricter than what OS would permit executing. + if (sb->st_uid == 0) + return S_ISREG(sb->st_mode) && (sb->st_mode & S_IXOTH); + + return false; +} + +static int ProgramLauncher_openAndCheckStat(const ProgramLauncher* this, ProgramFileRef* newFileRef) { + const char* path; + if (newFileRef) { + assert(!this->fileRef); + path = newFileRef->path; + } else { + assert(this->fileRef); + path = ((const ProgramFileRef*)this->fileRef)->path; + } + assert(path); + + int fd = -1; + int savedErrno = errno; + if (!(this->options & PROGRAM_LAUNCH_NO_SCRIPT)) { + int openFlags = O_RDONLY | O_NOCTTY | O_NONBLOCK; +#ifdef O_REGULAR + // O_REGULAR is supported in NetBSD. + openFlags |= O_REGULAR; +#endif + do { + fd = open(path, openFlags); + } while (fd < 0 && errno == EINTR); + + if (fd < 0 && errno != EACCES) { + return fd; + } + } + + if (fd < 0) { + int openFlags = O_NOCTTY | O_NONBLOCK; +#ifdef O_EXEC + openFlags |= O_EXEC; +#else + // O_EXEC is not supported in Linux. + openFlags |= O_RDONLY; +#endif +#ifdef O_PATH + // O_PATH is specific to Linux and FreeBSD. + openFlags |= O_PATH; +#endif +#ifdef O_CLOEXEC + openFlags |= O_CLOEXEC; +#endif +#ifdef O_REGULAR + // O_REGULAR is supported in NetBSD. + openFlags |= O_REGULAR; +#endif + do { + fd = open(path, openFlags); + } while (fd < 0 && errno == EINTR); + } + + if (fd < 0) + return fd; + + errno = savedErrno; + + struct stat sb; + int res = fstat(fd, &sb); + if (res != 0) { + savedErrno = errno; + goto fail; + } + + if (!newFileRef) { + const ProgramFileRef* fileRef = (const ProgramFileRef*)this->fileRef; + if (fileRef->dev != sb.st_dev || fileRef->ino != sb.st_ino) { + // The original file is gone and another file takes its place + // with the same name. Deny execution for safety. + savedErrno = ENOENT; + goto fail; + } + } + + if (!ProgramLauncher_canTrustExecStat(&sb)) { + savedErrno = EACCES; + goto fail; + } + + if (newFileRef) { + newFileRef->dev = sb.st_dev; + newFileRef->ino = sb.st_ino; + } + return fd; + +fail: + close(fd); + errno = savedErrno; + return -1; +} + +static bool ProgramLauncher_isScriptFile(int fd) { + assert(fd >= 0); + + char buf[2]; // This is intentionally not null terminated + ssize_t len = 0; + off_t start = 0; + do { + do { + len = pread(fd, buf + start, sizeof(buf) - (size_t)start, start); + } while (len < 0 && errno == EINTR); + + if (len <= 0) { + // End of file (len == 0) or a read error other than EINTR + return false; + } + start += len; + } while (start < (off_t)sizeof(buf)); + + assert(start == (off_t)sizeof(buf)); + return (buf[0] == '#' && buf[1] == '!'); +} + +void ProgramLauncher_setPath(ProgramLauncher* this, const char* path) { + if (this->lastErrno != 0) + return; + + ProgramFileRef* newFileRef = NULL; + const char* envPath = NULL; + char* csPathBuf = NULL; + if (this->fileRef) { + path = ((const ProgramFileRef*)this->fileRef)->path; + } else { + newFileRef = xMalloc(sizeof(*newFileRef)); + + if (!strchr(path, '/')) { + envPath = getenv("PATH"); + + if (!(envPath && envPath[0])) { + size_t csPathSize = confstr(_CS_PATH, NULL, 0); + if (csPathSize > sizeof("")) { + csPathBuf = xMalloc(csPathSize); + if (confstr(_CS_PATH, csPathBuf, csPathSize) == csPathSize) { + assert(csPathBuf[0] != '\0'); + envPath = csPathBuf; + } + } + } + + if (!(envPath && envPath[0])) { + this->lastErrno = ENOENT; + goto end; + } + } + } + + if (!(this->options & PROGRAM_LAUNCH_KEEP_SETUID)) + ProgramLauncher_dropSetUid(); + + const char* pathPrefix = envPath; + while (true) { + char* newPath = NULL; + const char* pathPrefixEnd = NULL; + if (newFileRef) { + if (!envPath) { + newPath = xStrdup(path); + } else { + pathPrefixEnd = String_strchrnul(pathPrefix, ':'); + assert(pathPrefixEnd >= pathPrefix); + + if (pathPrefixEnd > pathPrefix) { + int pathPrefixLen = (int)(pathPrefixEnd - pathPrefix) - (*(pathPrefixEnd - 1) == '/'); + assert(pathPrefixLen >= 0); + xAsprintf(&newPath, "%.*s/%s", pathPrefixLen, pathPrefix, path); + } else { + // POSIX allows zero-length prefix as "a legacy feature". + newPath = String_cat("./", path); + } + } + newFileRef->path = newPath; + } + + int fd = ProgramLauncher_openAndCheckStat(this, newFileRef); + // execlp() and execvp() from libc stop searching when there is a + // file with execute permission is found. They can stop searching + // even when the file has no read permission and the script + // interpreter would definitely fail on reading the script. + + if (fd >= 0) { + this->lastErrno = 0; + if (newFileRef) + this->fileRef = newFileRef; + + if (!(this->options & PROGRAM_LAUNCH_NO_SCRIPT) && !ProgramLauncher_isScriptFile(fd)) + this->options |= PROGRAM_LAUNCH_NO_SCRIPT; + + close(fd); + break; + } + + // Keep "Permission denied" error if there is one during the + // search. + if (this->lastErrno != EACCES) + this->lastErrno = errno; + + free(newPath); + + if (!(pathPrefixEnd && pathPrefixEnd[0])) + break; + + assert(pathPrefixEnd[0] == ':'); + pathPrefix = pathPrefixEnd + strlen(":"); + } + + if (!(this->options & PROGRAM_LAUNCH_KEEP_SETUID)) + ProgramLauncher_restoreSetUid(); + +end: + free(csPathBuf); + + assert(this->lastErrno != 0 || this->fileRef); + if (this->lastErrno != 0) { + free(newFileRef); + errno = this->lastErrno; + } +} + +void ProgramLauncher_execve(ProgramLauncher* this, char* const* argv, char* const* envp) { + if (!envp) + envp = environ; + + assert(this->fileRef); + assert(((const ProgramFileRef*)this->fileRef)->path); + if (!this->fileRef || !((const ProgramFileRef*)this->fileRef)->path) { + errno = EFAULT; + return; + } + + if (!(this->options & PROGRAM_LAUNCH_KEEP_SETUID)) { + // Permanently drop privileges + if (setgid(getgid()) != 0) { + return; + } + if (setuid(getuid()) != 0) { + return; + } + } + + int fd = ProgramLauncher_openAndCheckStat(this, NULL); + if (fd < 0) + return; + +#ifdef HAVE_FEXECVE + fexecve(fd, argv, envp); + + // Clean up if fexecve() fails + int savedErrno = errno; + close(fd); + errno = savedErrno; +#else + close(fd); + execve(((const ProgramFileRef*)this->fileRef)->path, argv, envp); +#endif +} diff --git a/ProgramLauncher.h b/ProgramLauncher.h new file mode 100644 index 000000000..dbac87bb5 --- /dev/null +++ b/ProgramLauncher.h @@ -0,0 +1,77 @@ +#ifndef HEADER_ProgramLauncher +#define HEADER_ProgramLauncher +/* +htop - ProgramLauncher.h +(C) 2026 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "Macros.h" + + +enum ProgramLauncherOptions_ { + /* Option to preserve the "setuid"/"setgid" privilege when launching + an external program (as opposed to dropping the privilege). + Be careful! It's often a security risk for leaking the privilege + to *any* third-party program. + This "setuid" privilege is different from "sudo" privilege! To + launch external programs with elevated privileges, it's very + likely you want to run "sudo htop" instead. */ + PROGRAM_LAUNCH_KEEP_SETUID = 1 << 0, + /* Option to disallow launching scripts from this module. Files that + begin with "#!" (shebang) and require an interpreter are scripts + in this context. */ + PROGRAM_LAUNCH_NO_SCRIPT = 1 << 2, +}; + +typedef unsigned int ProgramLauncherOptions; + +typedef struct ProgramLauncher_ { + void* fileRef; + int lastErrno; + ProgramLauncherOptions options; +} ProgramLauncher; + +// POSIX declares the type of "argv" and "envp" arguments in execve() +// as "char *const[]" for compability reason (see the "Ratinale" of +// ). +// Unfortunately that prevents the function from accepting the +// "const char *const[]" type, which is arguably more const-correct. +// An explicit cast from "char **" to "const char *const *" will +// generate a "-Wcast-qual" warning. Use this union as a workaround. +typedef union ExecStrPtrPtr_ { + const char* const* cpp; + char* const* pp; +} ExecStrPtrPtr; + +ATTR_NONNULL +void ProgramLauncher_done(ProgramLauncher* this); + +ATTR_NONNULL +void ProgramLauncher_setPath(ProgramLauncher* this, const char* path); + +ATTR_NONNULL_N(1, 2) +void ProgramLauncher_execve(ProgramLauncher* this, char* const* argv, char* const* envp); + +ATTR_NONNULL_N(1, 2) +static inline void ProgramLauncher_execve_const(ProgramLauncher* this, const char* const* argv, const char* const* envp) { + ExecStrPtrPtr argv_cast, envp_cast; + argv_cast.cpp = argv; + envp_cast.cpp = envp; + ProgramLauncher_execve(this, argv_cast.pp, envp_cast.pp); +} + +ATTR_NONNULL +static inline void ProgramLauncher_execv(ProgramLauncher* this, char* const* argv) { + ProgramLauncher_execve(this, argv, NULL); +} + +ATTR_NONNULL +static inline void ProgramLauncher_execv_const(ProgramLauncher* this, const char* const* argv) { + ExecStrPtrPtr argv_cast; + argv_cast.cpp = argv; + ProgramLauncher_execv(this, argv_cast.pp); +} + +#endif diff --git a/configure.ac b/configure.ac index 4f2b359eb..e4c087015 100644 --- a/configure.ac +++ b/configure.ac @@ -412,7 +412,9 @@ AC_SEARCH_LIBS([clock_gettime], [rt]) AC_CHECK_FUNCS([ \ dladdr \ faccessat \ + fexecve \ fstatat \ + group_member \ host_get_clock_service \ memfd_create \ openat \ From 664b158a852374b042a88e261c16c7a60af8345a Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 18 May 2026 16:10:18 +0800 Subject: [PATCH 2/6] SystemdMeter use ProgramLauncher to launch systemctl(1) Signed-off-by: Kang-Che Sung --- linux/SystemdMeter.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/linux/SystemdMeter.c b/linux/SystemdMeter.c index d594bf364..ae766f29d 100644 --- a/linux/SystemdMeter.c +++ b/linux/SystemdMeter.c @@ -21,6 +21,7 @@ in the source distribution for its full text. #include "CRT.h" #include "Macros.h" #include "Object.h" +#include "ProgramLauncher.h" #include "RichString.h" #include "Settings.h" #include "XUtils.h" @@ -68,6 +69,8 @@ typedef struct SystemdMeterContext { static SystemdMeterContext_t ctx_system; static SystemdMeterContext_t ctx_user; +static ProgramLauncher SystemdMeter_programLauncher; + static void SystemdMeter_done(ATTR_UNUSED Meter* this) { SystemdMeterContext_t* ctx = String_eq(Meter_name(this), "SystemdUser") ? &ctx_user : &ctx_system; @@ -217,6 +220,10 @@ static void updateViaExec(bool user) { if (Settings_isReadonly()) return; + ProgramLauncher_setPath(&SystemdMeter_programLauncher, "systemctl"); + if (SystemdMeter_programLauncher.lastErrno != 0) + return; + int fdpair[2] = {-1, -1}; if (pipe(fdpair) < 0) return; @@ -237,19 +244,20 @@ static void updateViaExec(bool user) { exit(1); dup2(fdnull, STDERR_FILENO); close(fdnull); - // Use of NULL in variadic functions must have a pointer cast. - // The NULL constant is not required by standard to have a pointer type. - execlp( - "systemctl", + + const char *argv[] = { "systemctl", "show", - user ? "--user" : "--system", + (user ? "--user" : "--system"), "--property=SystemState", "--property=NFailedUnits", "--property=NNames", "--property=NJobs", "--property=NInstalledJobs", - (char*)NULL); + NULL + }; + ProgramLauncher_execv_const(&SystemdMeter_programLauncher, argv); + exit(127); } close(fdpair[1]); From 236394b33c66d543152ad09e6cf3230483387239 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 18 May 2026 16:10:21 +0800 Subject: [PATCH 3/6] OpenRCMeter use ProgramLauncher to launch rc-status(8) Signed-off-by: Kang-Che Sung --- linux/OpenRCMeter.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/linux/OpenRCMeter.c b/linux/OpenRCMeter.c index 03f891eaf..f16ddba8c 100644 --- a/linux/OpenRCMeter.c +++ b/linux/OpenRCMeter.c @@ -20,6 +20,7 @@ in the source distribution for its full text. #include "CRT.h" #include "Macros.h" #include "Object.h" +#include "ProgramLauncher.h" #include "RichString.h" #include "Settings.h" #include "XUtils.h" @@ -35,6 +36,8 @@ typedef struct OpenRCMeterContext { static OpenRCMeterContext_t ctx_system; static OpenRCMeterContext_t ctx_user; +static ProgramLauncher OpenRCMeter_programLauncher; + static void OpenRCMeter_done(ATTR_UNUSED Meter* this) { OpenRCMeterContext_t* ctx = String_eq(Meter_name(this), "OpenRCUser") ? &ctx_user : &ctx_system; @@ -48,6 +51,10 @@ static void updateViaExec(bool user) { if (Settings_isReadonly()) return; + ProgramLauncher_setPath(&OpenRCMeter_programLauncher, "rc-status"); + if (OpenRCMeter_programLauncher.lastErrno != 0) + return; + int fdpair[2] = {-1, -1}; if (pipe(fdpair) < 0) return; @@ -68,11 +75,15 @@ static void updateViaExec(bool user) { exit(1); dup2(fdnull, STDERR_FILENO); close(fdnull); - if (user) { - execlp("rc-status", "rc-status", "--user", "-a", (char*)NULL); - } else { - execlp("rc-status", "rc-status", "-a", (char*)NULL); - } + + const char* argv[] = { + "rc-status", + "-a", + (user ? "--user" : NULL), + NULL + }; + ProgramLauncher_execv_const(&OpenRCMeter_programLauncher, argv); + exit(127); } close(fdpair[1]); From 4f06e76442e6c830ae8ca737f9599d4e63751bf9 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 18 May 2026 16:10:24 +0800 Subject: [PATCH 4/6] OpenFilesScreen use ProgramLauncher to launch lsof(8) Signed-off-by: Kang-Che Sung --- OpenFilesScreen.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index f8e802b5f..cba1003b5 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -22,6 +22,7 @@ in the source distribution for its full text. #include "Macros.h" #include "Panel.h" +#include "ProgramLauncher.h" #include "ProvideCurses.h" #include "Vector.h" #include "XUtils.h" @@ -46,6 +47,8 @@ typedef struct OpenFiles_FileData_ { struct OpenFiles_FileData_* next; } OpenFiles_FileData; +static ProgramLauncher OpenFiles_ProgramLauncher; + static size_t getIndexForType(char type) { switch (type) { case 'f': @@ -100,6 +103,12 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { pdata->cols[getIndexForType('o')] = 8; pdata->cols[getIndexForType('i')] = 8; + ProgramLauncher_setPath(&OpenFiles_ProgramLauncher, "lsof"); + if (OpenFiles_ProgramLauncher.lastErrno != 0) { + pdata->error = 1; + return pdata; + } + int fdpair[2] = {-1, -1}; if (pipe(fdpair) < 0) { pdata->error = 1; @@ -127,9 +136,18 @@ static OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) { close(fdnull); char buffer[32] = {0}; xSnprintf(buffer, sizeof(buffer), "%d", pid); - // Use of NULL in variadic functions must have a pointer cast. - // The NULL constant is not required by standard to have a pointer type. - execlp("lsof", "lsof", "-P", "-o", "-p", buffer, "-F", (char*)NULL); + + const char* argv[] = { + "lsof", + "-P", + "-o", + "-p", + buffer, + "-F", + NULL + }; + ProgramLauncher_execv_const(&OpenFiles_ProgramLauncher, argv); + exit(127); } close(fdpair[1]); From 45f5c73774036360875bb0f37e0af858e2f1ef0d Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 18 May 2026 16:10:27 +0800 Subject: [PATCH 5/6] TraceScreen use ProgramLauncher to launch trace program Signed-off-by: Kang-Che Sung --- TraceScreen.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/TraceScreen.c b/TraceScreen.c index f87700f2b..3225ef546 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -24,6 +24,7 @@ in the source distribution for its full text. #include "CRT.h" #include "FunctionBar.h" #include "Panel.h" +#include "ProgramLauncher.h" #include "ProvideCurses.h" #include "XUtils.h" @@ -34,6 +35,8 @@ static const char* const TraceScreenKeys[] = {"F3", "F4", "F8", "F9", "Esc"}; static const int TraceScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27}; +static ProgramLauncher TraceScreen_programLauncher; + TraceScreen* TraceScreen_new(const Process* process) { // This initializes all TraceScreen variables to "false" so only default = true ones need to be set below TraceScreen* this = xCalloc(1, sizeof(TraceScreen)); @@ -67,6 +70,16 @@ static void TraceScreen_draw(InfoScreen* this) { } bool TraceScreen_forkTracer(TraceScreen* this) { +#if defined(HTOP_FREEBSD) || defined(HTOP_OPENBSD) || defined(HTOP_NETBSD) || defined(HTOP_DRAGONFLYBSD) || defined(HTOP_SOLARIS) + ProgramLauncher_setPath(&TraceScreen_programLauncher, "truss"); +#elif defined(HTOP_LINUX) + ProgramLauncher_setPath(&TraceScreen_programLauncher, "strace"); +#else + TraceScreen_programLauncher.lastErrno = ENOSYS; +#endif + if (TraceScreen_programLauncher.lastErrno != 0) + return false; + int fdpair[2] = {-1, -1}; if (pipe(fdpair) < 0) @@ -93,15 +106,31 @@ bool TraceScreen_forkTracer(TraceScreen* this) { xSnprintf(buffer, sizeof(buffer), "%d", Process_getPid(this->super.process)); #if defined(HTOP_FREEBSD) || defined(HTOP_OPENBSD) || defined(HTOP_NETBSD) || defined(HTOP_DRAGONFLYBSD) || defined(HTOP_SOLARIS) - // Use of NULL in variadic functions must have a pointer cast. - // The NULL constant is not required by standard to have a pointer type. - execlp("truss", "truss", "-s", "512", "-p", buffer, (void*)NULL); + const char* argv[] = { + "truss", + "-s", + "512", + "-p", + buffer, + NULL + }; + ProgramLauncher_execv_const(&TraceScreen_programLauncher, argv); // Should never reach here, unless execlp fails ... const char* message = "Could not execute 'truss'. Please make sure it is available in your $PATH."; (void)! write(STDERR_FILENO, message, strlen(message)); #elif defined(HTOP_LINUX) - execlp("strace", "strace", "-T", "-tt", "-s", "512", "-p", buffer, (void*)NULL); + const char* argv[] = { + "strace", + "-T", + "-tt", + "-s", + "512", + "-p", + buffer, + NULL + }; + ProgramLauncher_execv_const(&TraceScreen_programLauncher, argv); // Should never reach here, unless execlp fails ... const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH."; From 4cd17d51e4e8554bd620f4747257732bdbc7d642 Mon Sep 17 00:00:00 2001 From: Explorer09 Date: Mon, 18 May 2026 16:10:29 +0800 Subject: [PATCH 6/6] TraceScreen print errno when exec fails Signed-off-by: Kang-Che Sung --- TraceScreen.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/TraceScreen.c b/TraceScreen.c index 3225ef546..6b74f0ad2 100644 --- a/TraceScreen.c +++ b/TraceScreen.c @@ -77,8 +77,6 @@ bool TraceScreen_forkTracer(TraceScreen* this) { #else TraceScreen_programLauncher.lastErrno = ENOSYS; #endif - if (TraceScreen_programLauncher.lastErrno != 0) - return false; int fdpair[2] = {-1, -1}; @@ -114,9 +112,14 @@ bool TraceScreen_forkTracer(TraceScreen* this) { buffer, NULL }; - ProgramLauncher_execv_const(&TraceScreen_programLauncher, argv); + if (TraceScreen_programLauncher.lastErrno == 0) { + ProgramLauncher_execv_const(&TraceScreen_programLauncher, argv); + } else { + errno = TraceScreen_programLauncher.lastErrno; + } // Should never reach here, unless execlp fails ... + fprintf(stderr, "Could not execute 'truss': %s\n", strerror(errno)); const char* message = "Could not execute 'truss'. Please make sure it is available in your $PATH."; (void)! write(STDERR_FILENO, message, strlen(message)); #elif defined(HTOP_LINUX) @@ -130,9 +133,14 @@ bool TraceScreen_forkTracer(TraceScreen* this) { buffer, NULL }; - ProgramLauncher_execv_const(&TraceScreen_programLauncher, argv); + if (TraceScreen_programLauncher.lastErrno == 0) { + ProgramLauncher_execv_const(&TraceScreen_programLauncher, argv); + } else { + errno = TraceScreen_programLauncher.lastErrno; + } // Should never reach here, unless execlp fails ... + fprintf(stderr, "Could not execute 'strace': %s\n", strerror(errno)); const char* message = "Could not execute 'strace'. Please make sure it is available in your $PATH."; (void)! write(STDERR_FILENO, message, strlen(message)); #else // HTOP_DARWIN, HTOP_PCP == HTOP_UNSUPPORTED