]> git.ipfire.org Git - people/ms/pakfire.git/blob - src/cli/lib/command.c
cli: Check for root privileges when needed
[people/ms/pakfire.git] / src / cli / lib / command.c
1 /*#############################################################################
2 # #
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2023 Pakfire development team #
5 # #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
18 # #
19 #############################################################################*/
20
21 #include <argp.h>
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27
28 #include "command.h"
29
30 static const struct command* command_find(const struct command* commands, const char* verb) {
31 for (const struct command* command = commands; command->verb; command++) {
32 if (strcmp(command->verb, verb) == 0)
33 return command;
34 }
35
36 return NULL;
37 }
38
39 static int count_arguments(int argc, char* argv[]) {
40 int arguments = 0;
41
42 for (int i = 1; i < argc; i++) {
43 if (*argv[i] == '-')
44 continue;
45
46 arguments++;
47 }
48
49 return arguments;
50 }
51
52 static int update_program_name(int argc, char* argv[], const char* verb) {
53 char* program_name = NULL;
54 int r;
55
56 // XXX maybe program_name should move into the ctx?
57
58 // Program name
59 r = asprintf(&program_name, "%s %s", program_invocation_short_name, verb);
60 if (r < 0)
61 return r;
62
63 program_invocation_short_name = argv[0] = program_name;
64
65 return 0;
66 }
67
68 static int command_run(const struct command* command, int argc, char* argv[], void* data) {
69 int r;
70
71 if (!command)
72 return -EINVAL;
73
74 // Update the program name
75 r = update_program_name(argc, argv, command->verb);
76 if (r)
77 return r;
78
79 // Run the command
80 return command->callback(data, argc, argv);
81 }
82
83 struct command_ctx {
84 const struct argp_option* options;
85 const struct command* commands;
86 command_parse parse;
87 int flags;
88 void* data;
89
90 // The selected command
91 const struct command* command;
92 int argc;
93 char** argv;
94 };
95
96 static error_t __command_parse(int key, char* arg, struct argp_state* state) {
97 struct command_ctx* ctx = state->input;
98
99 // Just call the parse function if we don't have any commands
100 if (!ctx->commands) {
101 if (!ctx->parse)
102 return ARGP_ERR_UNKNOWN;
103
104 return ctx->parse(key, arg, state, ctx->data);
105 }
106
107 switch (key) {
108 // Show help if no arguments have been passed
109 case ARGP_KEY_NO_ARGS:
110 argp_failure(state, EXIT_FAILURE, 0, "Missing command");
111 break;
112
113 // Try to find a command
114 case ARGP_KEY_ARG:
115 const struct command* command = ctx->command = command_find(ctx->commands, arg);
116
117 // Fail if the command wasn't found
118 if (!command) {
119 argp_failure(state, EXIT_FAILURE, 0, "Unknown command '%s'", arg);
120 break;
121 }
122
123 // XXX actually we should update the program name here
124
125 // Return UNKNOWN so that we get called for ARGP_KEY_ARGS
126 return ARGP_ERR_UNKNOWN;
127
128 // Store all remaining options & arguments
129 case ARGP_KEY_ARGS:
130 ctx->argc = state->argc - state->next;
131 ctx->argv = &state->argv[state->next];
132 break;
133
134 // Perform some final checks when parsing has been completed
135 case ARGP_KEY_SUCCESS:
136 // Check for root privileges
137 if (ctx->flags & CLI_REQUIRE_ROOT) {
138 if (getuid() || getgid())
139 argp_failure(state, EXIT_FAILURE, 0, "Must be run as root");
140 }
141
142 if (ctx->command) {
143 int args = count_arguments(ctx->argc, ctx->argv);
144
145 // Check if we have a sufficient number of arguments
146 if (ctx->command->min_args > 0 && args < ctx->command->min_args)
147 argp_error(state, "Not enough arguments");
148
149 else if (ctx->command->max_args >= 0 && args > ctx->command->max_args)
150 argp_error(state, "Too many arguments");
151 }
152 break;
153
154 // Do not pass any other things to the callback
155 case ARGP_KEY_END:
156 case ARGP_KEY_ERROR:
157 case ARGP_KEY_INIT:
158 case ARGP_KEY_FINI:
159 break;
160
161 // Otherwise call the callback
162 default:
163 if (!ctx->parse)
164 return ARGP_ERR_UNKNOWN;
165
166 return ctx->parse(key, arg, state, ctx->data);
167 }
168
169 return 0;
170 }
171
172 int cli_parse(const struct argp_option* options, const struct command* commands,
173 const char* args_doc, const char* doc,
174 command_parse parse, int flags, int argc, char** argv, void* data) {
175 int r;
176
177 // Setup context
178 struct command_ctx ctx = {
179 .options = options,
180 .commands = commands,
181 .parse = parse,
182 .flags = flags,
183 .data = data,
184 };
185
186 // Setup the parser
187 struct argp parser = {
188 .options = options,
189 .parser = __command_parse,
190 .args_doc = args_doc,
191 .doc = doc,
192 };
193 int arg_index = 0;
194
195 // Parse command line options
196 r = argp_parse(&parser, argc, argv, ARGP_IN_ORDER, &arg_index, &ctx);
197 if (r)
198 return r;
199
200 // Dispatch the selected command
201 if (commands) {
202 // Run the command
203 r = command_run(ctx.command, ctx.argc, ctx.argv, ctx.data);
204 if (r)
205 return r;
206 }
207
208 return 0;
209 }