master ohmu/fastfetch-openbsd / src / common / impl / processing_linux.c
ohmu  ·  2026-10-15
  1#include "fastfetch.h"
  2#include "common/debug.h"
  3#include "common/processing.h"
  4#include "common/io.h"
  5#include "common/strutil.h"
  6#include "common/mallocHelper.h"
  7
  8#include <stdlib.h>
  9#include <unistd.h>
 10#include <signal.h>
 11#include <poll.h>
 12#include <fcntl.h>
 13#include <errno.h>
 14#include <sys/wait.h>
 15
 16#if !(__ANDROID__ || __OpenBSD__)
 17    #include <spawn.h>
 18#endif
 19
 20#if defined(__FreeBSD__) || defined(__APPLE__)
 21    #include <sys/types.h>
 22    #include <sys/user.h>
 23    #include <sys/sysctl.h>
 24#endif
 25#if defined(__APPLE__)
 26    #include <libproc.h>
 27#elif defined(__sun)
 28    #include <procfs.h>
 29#elif defined(__OpenBSD__)
 30    #include <sys/param.h>
 31    #include <sys/sysctl.h>
 32#elif defined(__NetBSD__)
 33    #include <sys/types.h>
 34    #include <sys/sysctl.h>
 35#elif defined(__HAIKU__)
 36    #include <OS.h>
 37    #include <image.h>
 38#endif
 39
 40#ifndef environ
 41extern char** environ;
 42#endif
 43
 44enum { FF_PIPE_BUFSIZ = 8192 };
 45
 46static inline int ffPipe2(int* readEnd, int* writeEnd, int flags) {
 47    int fds[2];
 48#ifndef FF_HAVE_PIPE2
 49    if (pipe(fds) == -1) {
 50        return -1;
 51    }
 52    fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | flags);
 53    fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | flags);
 54#else
 55    if (pipe2(fds, flags) == -1) {
 56        return -1;
 57    }
 58#endif
 59    *readEnd = fds[0];
 60    *writeEnd = fds[1];
 61    return 0;
 62}
 63
 64// Not thread-safe
 65const char* ffProcessSpawn(char* const argv[], FFProcessOutputType useOutput, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
 66    FF_AUTO_CLOSE_FD int pipeRead = -1;
 67    FF_AUTO_CLOSE_FD int pipeWrite = -1;
 68    if (ffPipe2(&pipeRead, &pipeWrite, O_CLOEXEC) == -1) {
 69        FF_DEBUG("pipe() failed: %s", strerror(errno));
 70        return "pipe() failed";
 71    }
 72
 73    pid_t childPid = -1;
 74    int nullFile = ffGetNullFD();
 75    // Each stream is wired to the pipe when its bit is set and to the null device otherwise, so an
 76    // unselected stream can neither mix into the captured text nor fill the pipe and block the child.
 77    int stdoutFd = (useOutput & FF_PROCESS_OUTPUT_STDOUT_BIT) ? pipeWrite : nullFile;
 78    int stderrFd = (useOutput & FF_PROCESS_OUTPUT_STDERR_BIT) ? pipeWrite : nullFile;
 79
 80#if !(__ANDROID__ || __OpenBSD__)
 81
 82    // NetBSD / Darwin: native syscall
 83    // Linux (glibc): clone3-execve
 84    // FreeBSD: vfork-execve
 85    // illumos: vforkx-execve
 86    // OpenBSD / Android (bionic): fork-execve
 87
 88    posix_spawn_file_actions_t file_actions;
 89    posix_spawn_file_actions_init(&file_actions);
 90    posix_spawn_file_actions_adddup2(&file_actions, stdoutFd, STDOUT_FILENO);
 91    posix_spawn_file_actions_adddup2(&file_actions, stderrFd, STDERR_FILENO);
 92    if (ffIsValidNativeFD(stdinFd)) {
 93        posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO);
 94    }
 95
 96    static char* oldLang = nullptr;
 97    static int langIndex = -1;
 98
 99    if (langIndex >= 0) {
100        // Found before
101        if (oldLang) // oldLang was set only if it needed to be changed
102        {
103            if (environ[langIndex] != oldLang) {
104                // environ is changed outside of this function
105                langIndex = -1;
106            } else {
107                environ[langIndex] = (char*) "LANG=C.UTF-8";
108            }
109        }
110    }
111    if (langIndex < 0) {
112        for (int i = 0; environ[i] != nullptr; i++) {
113            if (ffStrStartsWith(environ[i], "LANG=")) {
114                langIndex = i;
115                const char* langValue = environ[i] + 5; // Skip "LANG="
116                if (ffStrEqualsIgnCase(langValue, "C") ||
117                    ffStrStartsWithIgnCase(environ[i], "C.") ||
118                    ffStrEqualsIgnCase(langValue, "en_US") ||
119                    ffStrStartsWithIgnCase(langValue, "en_US.")) {
120                    break; // No need to change LANG
121                }
122                oldLang = environ[i];
123                environ[i] = (char*) "LANG=C.UTF-8"; // Set LANG to C.UTF-8 for consistent output
124                break;
125            }
126        }
127    }
128
129    int ret = posix_spawnp(&childPid, argv[0], &file_actions, nullptr, argv, environ);
130
131    if (oldLang) {
132        environ[langIndex] = oldLang;
133    }
134
135    posix_spawn_file_actions_destroy(&file_actions);
136
137    if (ret != 0) {
138        FF_DEBUG("posix_spawnp() failed: %s", strerror(ret));
139        if (ret == ENOENT) {
140            return "command not found";
141        }
142        return "posix_spawnp() failed";
143    }
144
145#else
146
147    // https://github.com/termux/termux-packages/issues/25369
148    childPid = fork();
149    if (childPid == -1) {
150        FF_DEBUG("fork() failed: %s", strerror(errno));
151        return "fork() failed";
152    }
153
154    if (childPid == 0) {
155        // Child process
156        dup2(stdoutFd, STDOUT_FILENO);
157        dup2(stderrFd, STDERR_FILENO);
158        if (ffIsValidNativeFD(stdinFd)) {
159            dup2(stdinFd, STDIN_FILENO);
160        }
161        putenv("LANG=C.UTF-8");
162        execvp(argv[0], argv);
163        _exit(127);
164    }
165
166#endif
167
168    outHandle->pid = childPid;
169    outHandle->pipeRead = pipeRead;
170    pipeRead = -1; // ownership moved into outHandle, which ffProcessReadOutput() lets go
171    return nullptr;
172}
173
174const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
175    assert(handle->pipeRead != -1);
176    assert(handle->pid != -1);
177
178    const int32_t timeout = instance.config.general.processingTimeout;
179    FF_AUTO_CLOSE_FD int childPipeFd = handle->pipeRead;
180    pid_t childPid = handle->pid;
181    handle->pipeRead = -1;
182    handle->pid = -1;
183    char str[FF_PIPE_BUFSIZ];
184
185    for (;;) {
186        if (timeout >= 0) {
187            struct pollfd pollfd = { childPipeFd, POLLIN, 0 };
188            int pollret = poll(&pollfd, 1, timeout);
189            if (pollret == 0) {
190                FF_DEBUG("poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)");
191                kill(childPid, SIGTERM);
192                waitpid(childPid, nullptr, 0);
193                return "poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)";
194            } else if (pollret < 0 || (pollfd.revents & POLLERR)) {
195                kill(childPid, SIGTERM);
196                waitpid(childPid, nullptr, 0);
197                return pollret < 0
198                    ? "poll(&pollfd, 1, timeout) error: pollret < 0"
199                    : "poll(&pollfd, 1, timeout) error: pollfd.revents & POLLERR";
200            }
201        }
202
203        ssize_t nRead = read(childPipeFd, str, FF_PIPE_BUFSIZ);
204        if (nRead > 0) {
205            ffStrbufAppendNS(buffer, (uint32_t) nRead, str);
206        } else if (nRead == 0) {
207            int stat_loc = 0;
208            if (childPid > 0 && waitpid(childPid, &stat_loc, 0) == childPid) {
209                if (!WIFEXITED(stat_loc)) {
210                    return "child process exited abnormally";
211                }
212                if (WEXITSTATUS(stat_loc) == 127) {
213                    FF_DEBUG("command not found");
214                    return "command not found";
215                }
216                // We only handle 127 as an error. See `getTerminalVersionUrxvt` in `terminalshell.c`
217                return nullptr;
218            }
219            return nullptr;
220        } else if (nRead < 0) {
221            break;
222        }
223    }
224
225    FF_DEBUG("read(childPipeFd, str, FF_PIPE_BUFSIZ) failed: %s", strerror(errno));
226    return "read(childPipeFd, str, FF_PIPE_BUFSIZ) failed";
227}
228
229void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, const char** exeName, FFstrbuf* exePath) {
230    assert(processName->length > 0);
231    ffStrbufClear(exe);
232    if (exePath) {
233        ffStrbufClear(exePath);
234    }
235
236#if defined(__linux__) || defined(__GNU__)
237
238    char filePath[64];
239    snprintf(filePath, sizeof(filePath), "/proc/%d/cmdline", (int) pid);
240
241    if (ffReadFileBuffer(filePath, exe)) {
242        const char* p = exe->chars;
243        uint32_t len = (uint32_t) strlen(p);
244
245        if (len + 1 < exe->length) {
246            const char* name = memrchr(p, '/', len);
247            if (name) {
248                name++;
249            } else {
250                name = p;
251            }
252
253            // For interpreters, try to find the real script path in the arguments
254            if (ffStrStartsWith(name, "python")
255    #ifndef __ANDROID__
256                || ffStrEquals(name, "guile") // for shepherd
257    #endif
258            ) {
259                // `cmdline` always ends with a trailing '\0', and ffReadFileBuffer appends another \0
260                // So `exe->chars` is always double '\0' terminated
261                for (p = p + len + 1; *p && *p == '-'; p += strlen(p) + 1) { // Skip arguments
262                    assert(p - exe->chars < exe->allocated);
263                }
264                if (*p) {
265                    len = (uint32_t) strlen(p);
266                    memmove(exe->chars, p, len + 1);
267                }
268            }
269        }
270
271        assert(len < exe->allocated);
272        exe->length = len;
273        ffStrbufTrimLeft(exe, '-'); // Login shells start with a dash
274    }
275
276    if (exePath) {
277        snprintf(filePath, sizeof(filePath), "/proc/%d/exe", (int) pid);
278        char buf[PATH_MAX];
279        ssize_t length = readlink(filePath, buf, PATH_MAX - 1);
280        if (length > 0) // doesn't contain trailing NUL
281        {
282            buf[length] = '\0';
283            // When the process is a deleted executable, the resolved path is like `/usr/bin/app (deleted)`
284            // But we can still access the binary via `/proc/pid/exe`. See #2136
285            if (ffPathExists(buf, FF_PATHTYPE_ANY)) {
286                ffStrbufSetNS(exePath, (uint32_t) length, buf);
287            }
288        }
289
290        if (exePath->length == 0 && access(filePath, X_OK) == 0) {
291            ffStrbufSetS(exePath, filePath);
292        }
293    }
294
295#elif defined(__APPLE__)
296
297    size_t len = 0;
298    int mibs[] = { CTL_KERN, KERN_PROCARGS2, pid };
299    if (sysctl(mibs, ARRAY_SIZE(mibs), nullptr, &len, nullptr, 0) == 0) { // try get arg0
300        // don't know why if don't let len longer, proArgs2 and len will change during the following sysctl() in old MacOS version.
301        len++;
302        FF_AUTO_FREE char* const procArgs2 = malloc(len);
303        if (sysctl(mibs, ARRAY_SIZE(mibs), procArgs2, &len, nullptr, 0) == 0) {
304            // https://gist.github.com/nonowarn/770696#file-getargv-c-L46
305            uint32_t argc = *(uint32_t*) procArgs2;
306            const char* realExePath = procArgs2 + sizeof(argc);
307
308            const char* arg0 = memchr(realExePath, '\0', len - (size_t) (realExePath - procArgs2));
309            if (exePath) {
310                ffStrbufSetNS(exePath, (uint32_t) (arg0 - realExePath), realExePath);
311            }
312
313            do {
314                arg0++;
315            } while (*arg0 == '\0');
316            assert(arg0 < procArgs2 + len);
317
318            if (argc > 1) {
319                // #977
320                const char* p = strrchr(arg0, '/');
321                if (p) {
322                    p++;
323                } else {
324                    p = arg0;
325                }
326                if (ffStrStartsWithIgnCase(p, "python")) { // /opt/homebrew/Cellar/python@3.12/3.12.3/Frameworks/Python.framework/Versions/3.12/Resources/Python.app/Contents/MacOS/Python /Users/carter/.local/bin/xonsh
327                    arg0 = p + strlen(p) + 1;
328                }
329            }
330
331            if (*arg0 == '-') {
332                arg0++; // Login shells
333            }
334
335            ffStrbufSetS(exe, arg0);
336        }
337    }
338
339    if (exePath || exe->length == 0) {
340        char buf[PROC_PIDPATHINFO_MAXSIZE];
341        int length = proc_pidpath(pid, buf, ARRAY_SIZE(buf));
342        if (length > 0) {
343            if (exe->length == 0) {
344                ffStrbufSetNS(exe, (uint32_t) length, buf);
345            }
346            if (exePath) {
347                // We don't use exec_path above as exePath because it's a relative path and can be different
348                // from the actual executable being run (for example, when the original file is moved)
349                ffStrbufSetNS(exePath, (uint32_t) length, buf);
350            }
351        }
352    }
353
354#elif defined(__FreeBSD__) || defined(__NetBSD__)
355
356    size_t size = ARG_MAX;
357    FF_AUTO_FREE char* args = malloc(size);
358
359    static_assert(ARG_MAX > PATH_MAX, "");
360
361    if (exePath && sysctl((int[]) { CTL_KERN,
362    #if __FreeBSD__
363                              KERN_PROC,
364                              KERN_PROC_PATHNAME,
365                              pid
366    #else
367                              KERN_PROC_ARGS,
368                              pid,
369                              KERN_PROC_PATHNAME
370    #endif
371                          },
372                       4,
373                       args,
374                       &size,
375                       nullptr,
376                       0) == 0)
377        ffStrbufSetNS(exePath, (uint32_t) (size - 1), args);
378
379    size = ARG_MAX;
380    if (sysctl(
381            (int[]) { CTL_KERN,
382    #if __FreeBSD__
383                KERN_PROC,
384                KERN_PROC_ARGS,
385                pid
386    #else
387                KERN_PROC_ARGS,
388                pid,
389                KERN_PROC_ARGV,
390    #endif
391            },
392            4,
393            args,
394            &size,
395            nullptr,
396            0) == 0) {
397        char* arg0 = args;
398        size_t arg0Len = strlen(args);
399        if (size > arg0Len + 1) {
400            char* p = (char*) memrchr(args, '/', arg0Len);
401            if (p) {
402                p++;
403            } else {
404                p = arg0;
405            }
406            if (ffStrStartsWith(p, "python")) // /usr/local/bin/python3.9 /home/carter/.local/bin/xonsh
407            {
408                arg0 += arg0Len + 1;
409            }
410        }
411        if (arg0[0] == '-') {
412            arg0++;
413        }
414        ffStrbufSetS(exe, arg0);
415    }
416
417#elif defined(__sun)
418
419    char filePath[128];
420    snprintf(filePath, sizeof(filePath), "/proc/%d/psinfo", (int) pid);
421    psinfo_t proc;
422    if (ffReadFileData(filePath, sizeof(proc), &proc) == sizeof(proc)) {
423        const char* args = proc.pr_psargs;
424        if (args[0] == '-') {
425            ++args;
426        }
427        const char* end = strchr(args, ' ');
428        ffStrbufSetNS(exe, end ? (uint32_t) (end - args) : (uint32_t) strlen(args), args);
429    }
430
431    if (exePath) {
432        snprintf(filePath, sizeof(filePath), "/proc/%d/path/a.out", (int) pid);
433        char buf[PATH_MAX];
434        ssize_t length = readlink(filePath, buf, PATH_MAX - 1);
435        if (length > 0) // doesn't contain trailing NUL
436        {
437            buf[length] = '\0';
438            ffStrbufSetNS(exePath, (uint32_t) length, buf);
439        }
440    }
441
442#elif defined(__OpenBSD__)
443
444    char argvBuf[ARG_MAX];
445    size_t argvSize = sizeof(argvBuf);
446    int argvMib[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV };
447    if (sysctl(argvMib, ARRAY_SIZE(argvMib), argvBuf, &argvSize, nullptr, 0) == 0) {
448        // The buffer is filled with an array of char pointers followed by the strings themselves
449        char** argv = (char**) argvBuf;
450        if (argv[0] && (char*) argv[0] >= argvBuf && (char*) argv[0] < argvBuf + argvSize) {
451            const char* arg0 = argv[0];
452            if (arg0[0] == '-') {
453                arg0++;
454            }
455            ffStrbufSetS(exe, arg0);
456        }
457    }
458
459#elif defined(__HAIKU__)
460
461    image_info info;
462    int32 cookie = 0;
463
464    while (get_next_image_info(pid, &cookie, &info) == B_OK) {
465        if (info.type != B_APP_IMAGE) {
466            continue;
467        }
468        ffStrbufSetS(exe, info.name);
469
470        if (exePath) {
471            ffStrbufSet(exePath, exe);
472        }
473        break;
474    }
475
476#endif
477
478    if (exe->length == 0) {
479        ffStrbufSet(exe, processName);
480    }
481
482    assert(exe->length > 0);
483    uint32_t lastSlashIndex = ffStrbufLastIndexC(exe, '/');
484    if (lastSlashIndex < exe->length) {
485        *exeName = exe->chars + lastSlashIndex + 1;
486    }
487}
488
489const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty) {
490    if (pid <= 0) {
491        FF_DEBUG("Invalid pid");
492        return "Invalid pid";
493    }
494
495#if defined(__linux__) || defined(__GNU__)
496
497    char procFilePath[64];
498    #if __linux__
499    if (ppid || tty)
500    #endif
501    {
502        snprintf(procFilePath, sizeof(procFilePath), "/proc/%d/stat", (int) pid);
503        char buf[PROC_FILE_BUFFSIZ];
504        ssize_t nRead = ffReadFileData(procFilePath, sizeof(buf) - 1, buf);
505        if (nRead <= 8) {
506            FF_DEBUG("ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed");
507            return "ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed";
508        }
509        buf[nRead] = '\0'; // pid (comm) state ppid pgrp session tty
510
511        const char* pState = nullptr;
512
513        {
514            // comm in `/proc/pid/stat` is not encoded, and may contain ' ', ')' or even `\n`
515            const char* start = memchr(buf, '(', (size_t) nRead);
516            if (!start) {
517                FF_DEBUG("memchr(stat, '(') failed");
518                return "memchr(stat, '(') failed";
519            }
520            start++;
521            const char* end = memrchr(start, ')', (size_t) nRead - (size_t) (start - buf));
522            if (!end) {
523                FF_DEBUG("memrchr(stat, ')') failed");
524                return "memrchr(stat, ')') failed";
525            }
526            ffStrbufSetNS(name, (uint32_t) (end - start), start);
527            ffStrbufTrimRightSpace(name);
528            if (name->chars[0] == '\0') {
529                FF_DEBUG("process name is empty");
530                return "process name is empty";
531            }
532            pState = end + 2; // skip ") "
533        }
534
535    #if !__linux__
536        if (ppid || tty)
537    #endif
538        {
539            int ppid_, tty_;
540            if (sscanf(pState + 2, "%d %*d %*d %d", &ppid_, &tty_) < 2) {
541                FF_DEBUG("sscanf(stat) failed");
542                return "sscanf(stat) failed";
543            }
544
545            if (ppid) {
546                *ppid = (pid_t) ppid_;
547            }
548            if (tty) {
549                *tty = tty_ & 0xFF;
550            }
551        }
552    }
553    #if __linux__
554    else {
555        snprintf(procFilePath, sizeof(procFilePath), "/proc/%d/comm", (int) pid);
556        ssize_t nRead = ffReadFileBuffer(procFilePath, name);
557        if (nRead <= 0) {
558            FF_DEBUG("ffReadFileBuffer(/proc/pid/comm, name) failed");
559            return "ffReadFileBuffer(/proc/pid/comm, name) failed";
560        }
561        ffStrbufTrimRightSpace(name);
562    }
563    #endif
564
565#elif defined(__APPLE__)
566
567    struct kinfo_proc proc;
568    size_t size = sizeof(proc);
569    if (sysctl(
570            (int[]) { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }, 4, &proc, &size, nullptr, 0)) {
571        FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
572        return "sysctl(KERN_PROC_PID) failed";
573    }
574
575    ffStrbufSetS(name, proc.kp_proc.p_comm); // truncated to 16 chars
576    if (ppid) {
577        *ppid = (pid_t) proc.kp_eproc.e_ppid;
578    }
579    if (tty) {
580        *tty = ((proc.kp_eproc.e_tdev >> 24) & 0xFF) == 0x10
581            ? proc.kp_eproc.e_tdev & 0xFFFFFF
582            : -1;
583    }
584
585#elif defined(__FreeBSD__)
586
587    #ifdef __DragonFly__
588        #define ki_comm kp_comm
589        #define ki_ppid kp_ppid
590        #define ki_tdev kp_tdev
591        #define ki_flag kp_flags
592    #endif
593
594    struct kinfo_proc proc;
595    size_t size = sizeof(proc);
596    if (sysctl(
597            (int[]) { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }, 4, &proc, &size, nullptr, 0)) {
598        FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
599        return "sysctl(KERN_PROC_PID) failed";
600    }
601
602    ffStrbufSetS(name, proc.ki_comm);
603    if (ppid) {
604        *ppid = (pid_t) proc.ki_ppid;
605    }
606    if (tty) {
607        if (proc.ki_tdev != NODEV && proc.ki_flag & P_CONTROLT) {
608            const char* ttyName = devname(proc.ki_tdev, S_IFCHR);
609            if (ffStrStartsWith(ttyName, "pts/")) {
610                *tty = (int32_t) strtol(ttyName + strlen("pts/"), nullptr, 10);
611            } else {
612                *tty = -1;
613            }
614        } else {
615            *tty = -1;
616        }
617    }
618
619#elif defined(__NetBSD__)
620
621    struct kinfo_proc2 proc;
622    size_t size = sizeof(proc);
623    if (sysctl(
624            (int[]) { CTL_KERN, KERN_PROC2, KERN_PROC_PID, pid, sizeof(proc), 1 }, 6, &proc, &size, nullptr, 0) != 0) {
625        FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
626        return "sysctl(KERN_PROC_PID) failed";
627    }
628
629    ffStrbufSetS(name, proc.p_comm);
630    if (ppid) {
631        *ppid = (pid_t) proc.p_ppid;
632    }
633    if (tty) {
634        if (proc.p_flag & P_CONTROLT) {
635            const char* ttyName = devname(proc.p_tdev, S_IFCHR);
636            if (ffStrStartsWith(ttyName, "pts/")) {
637                *tty = (int32_t) strtol(ttyName + strlen("pts/"), nullptr, 10);
638            } else {
639                *tty = -1;
640            }
641        } else {
642            *tty = -1;
643        }
644    }
645
646#elif defined(__sun)
647    char path[128];
648    snprintf(path, sizeof(path), "/proc/%d/psinfo", (int) pid);
649    psinfo_t proc;
650    if (ffReadFileData(path, sizeof(proc), &proc) != sizeof(proc)) {
651        FF_DEBUG("ffReadFileData(psinfo) failed");
652        return "ffReadFileData(psinfo) failed";
653    }
654
655    ffStrbufSetS(name, proc.pr_fname);
656    if (ppid) {
657        *ppid = proc.pr_ppid;
658    }
659    if (tty) {
660        *tty = (int) proc.pr_ttydev;
661    }
662
663#elif defined(__OpenBSD__)
664
665    struct kinfo_proc proc;
666    size_t size = sizeof(proc);
667    int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, (int) sizeof(struct kinfo_proc), 1 };
668    if (sysctl(mib, ARRAY_SIZE(mib), &proc, &size, nullptr, 0) == 0) {
669        ffStrbufSetS(name, proc.p_comm);
670        if (ppid) {
671            *ppid = proc.p_ppid;
672        }
673        if (tty) {
674            *tty = (int) proc.p_tdev;
675        }
676    } else {
677        FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
678        return "sysctl(KERN_PROC_PID) failed";
679    }
680
681#elif defined(__HAIKU__)
682
683    team_info info;
684    if (get_team_info(pid, &info) == B_OK) {
685        ffStrbufSetS(name, info.name);
686        if (ppid) {
687            *ppid = info.parent;
688        }
689    }
690
691    FF_UNUSED(tty);
692
693#else
694
695    FF_DEBUG("Unsupported platform");
696    return "Unsupported platform";
697
698#endif
699
700    return nullptr;
701}