| 133 | } |
| 134 | |
| 135 | cbm_daemon_process_role_t cbm_daemon_process_role(int argc, char *const argv[]) { |
| 136 | if (argc <= 0 || !argv || !argv[0] || argv[0][0] == '\0') { |
| 137 | return CBM_DAEMON_PROCESS_INVALID; |
| 138 | } |
| 139 | |
| 140 | int daemon_arg = bootstrap_find_arg(argc, argv, CBM_DAEMON_INTERNAL_ARG); |
| 141 | if (daemon_arg >= 0) { |
| 142 | /* Byte-exact daemon-role grammar, deliberately unforgiving: the bare |
| 143 | * internal marker, or the marker followed by exactly the permanent |
| 144 | * flag. Every other shape — reordered, repeated, or extended — is |
| 145 | * INVALID, so argv smuggling cannot reach the daemon role. */ |
| 146 | if (argc == 2 && daemon_arg == 1) { |
| 147 | return CBM_DAEMON_PROCESS_DAEMON; |
| 148 | } |
| 149 | if (argc == 3 && daemon_arg == 1 && bootstrap_arg_is(argv[2], CBM_DAEMON_PERMANENT_ARG)) { |
| 150 | return CBM_DAEMON_PROCESS_DAEMON; |
| 151 | } |
| 152 | return CBM_DAEMON_PROCESS_INVALID; |
| 153 | } |
| 154 | |
| 155 | int worker_arg = bootstrap_find_arg(argc, argv, "--index-worker"); |
| 156 | if (worker_arg >= 0) { |
| 157 | return bootstrap_worker_argv_exact(argc, argv) ? CBM_DAEMON_PROCESS_WORKER |
| 158 | : CBM_DAEMON_PROCESS_INVALID; |
| 159 | } |
| 160 | |
| 161 | static const char *const stateless_commands[] = { |
| 162 | "install", |
| 163 | "uninstall", |
| 164 | "update", |
| 165 | /* allow-root writes one line of user-level config and reads nothing from a |
| 166 | * project, so it needs no daemon. Listed here rather than routed through |
| 167 | * the daemon so enrolling a root cannot depend on daemon state. */ |
| 168 | "allow-root", |
| 169 | }; |
| 170 | /* Stop at the first top-level mode token. Tool names, flag values, and JSON |
| 171 | * following `cli` are opaque user input: a search query named "install" |
| 172 | * or containing "--version" must never bypass the mandatory daemon. */ |
| 173 | for (int arg = 1; arg < argc; arg++) { |
| 174 | if (bootstrap_arg_is(argv[arg], "cli")) { |
| 175 | if (bootstrap_has_help_after(argc, argv, arg + 1)) { |
| 176 | return CBM_DAEMON_PROCESS_STATELESS; |
| 177 | } |
| 178 | return CBM_DAEMON_PROCESS_LOCAL_CLI; |
| 179 | } |
| 180 | if (bootstrap_arg_is(argv[arg], "hook-augment")) { |
| 181 | return CBM_DAEMON_PROCESS_HOOK_CLIENT; |
| 182 | } |
| 183 | if (bootstrap_arg_is(argv[arg], "config")) { |
| 184 | return bootstrap_has_help_after(argc, argv, arg + 1) ? CBM_DAEMON_PROCESS_STATELESS |
| 185 | : CBM_DAEMON_PROCESS_LOCAL_CLI; |
| 186 | } |
| 187 | /* Placed after the `cli` check on purpose: `cbm cli search "daemon |
| 188 | * start"` is opaque tool input and must stay LOCAL_CLI. */ |
| 189 | if (bootstrap_arg_is(argv[arg], "daemon")) { |
| 190 | return bootstrap_has_help_after(argc, argv, arg + 1) ? CBM_DAEMON_PROCESS_STATELESS |
| 191 | : CBM_DAEMON_PROCESS_DAEMON_CTL; |
| 192 | } |