master ohmu/fastfetch-openbsd / src / detection / command / command.c
ohmu  ·  2026-10-15
 1#include "detection/command/command.h"
 2#include "common/processing.h"
 3#include "common/FFstrbuf.h"
 4
 5typedef struct FFCommandResultBundle {
 6    FFProcessHandle handle;
 7    const char* error;
 8} FFCommandResultBundle;
 9
10// FIFO, non-thread-safe list of running commands
11static FFlist commandQueue;
12
13static const char* spawnProcess(FFCommandOptions* options, FFProcessHandle* handle) {
14    if (options->text.length == 0) {
15        return "No command text specified";
16    }
17
18#if _WIN32
19    if (ffStrbufEqualS(&options->shell, "cmd.exe") && ffStrbufEqualS(&options->param, "/d/c")) {
20        ffStrbufAppendS(&options->text, " & exit %^errorlevel%"); // Necessary to propagate 9009 from CMD
21    }
22#endif
23
24    return ffProcessSpawn(options->param.length ? (char* const[]) {
25                                                      options->shell.chars,
26                                                      options->param.chars,
27                                                      options->text.chars,
28                                                      nullptr }
29                                                : (char* const[]) { options->shell.chars, options->text.chars, nullptr },
30        options->useOutput,
31        FF_PROCESS_INHERIT_STDIN,
32        handle);
33}
34
35bool ffPrepareCommand(FFCommandOptions* options) {
36    if (!options->parallel) {
37        return false;
38    }
39
40    FFCommandResultBundle* bundle = FF_LIST_ADD(FFCommandResultBundle, commandQueue);
41    bundle->error = spawnProcess(options, &bundle->handle);
42
43    return true;
44}
45
46const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result) {
47    FFCommandResultBundle bundle = {};
48    if (!options->parallel) {
49        bundle.error = spawnProcess(options, &bundle.handle);
50    } else if (!FF_LIST_SHIFT(commandQueue, &bundle)) {
51        // The module was printed without having been prepared first, so there is no process to collect.
52        // That is the expected outcome for a module gated on `condition.succeeded`: whether it is
53        // printed at all is only known in the print pass, so the prepare pass skips it (see
54        // `printJsonConfig`). In every other case the queue and the printed modules must line up.
55        return "Module was not prepared, so it cannot run in parallel. Modules gated on "
56               "`condition.succeeded` are never prepared, which is the expected cause; "
57               "anything else is a bug in fastfetch, please report it";
58    }
59
60    if (bundle.error) {
61        return bundle.error;
62    }
63
64    bundle.error = ffProcessReadOutput(&bundle.handle, result);
65    if (bundle.error) {
66        return bundle.error;
67    }
68
69    ffStrbufTrimRightSpace(result);
70    return nullptr;
71}