Commit | Line | Data |
---|---|---|
1479465f GJ |
1 | /* |
2 | * libdpkg - Debian packaging suite library routines | |
3 | * options.c - option parsing functions | |
4 | * | |
5 | * Copyright © 1994,1995 Ian Jackson <ijackson@chiark.greenend.org.uk> | |
6 | * Copyright © 2000,2002 Wichert Akkerman <wichert@deephackmode.org> | |
7 | * Copyright © 2008-2015 Guillem Jover <guillem@debian.org> | |
8 | * | |
9 | * This is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
21 | */ | |
22 | ||
23 | #include <config.h> | |
24 | #include <compat.h> | |
25 | ||
26 | #include <errno.h> | |
27 | #include <limits.h> | |
28 | #include <string.h> | |
29 | #include <dirent.h> | |
30 | #include <stdarg.h> | |
31 | #include <stdlib.h> | |
32 | ||
33 | #include <dpkg/i18n.h> | |
34 | #include <dpkg/c-ctype.h> | |
35 | #include <dpkg/dpkg.h> | |
36 | #include <dpkg/string.h> | |
37 | #include <dpkg/options.h> | |
38 | ||
39 | static const char *printforhelp; | |
40 | ||
41 | void | |
42 | badusage(const char *fmt, ...) | |
43 | { | |
44 | char *buf = NULL; | |
45 | va_list args; | |
46 | ||
47 | va_start(args, fmt); | |
48 | m_vasprintf(&buf, fmt, args); | |
49 | va_end(args); | |
50 | ||
51 | ohshit("%s\n\n%s", buf, gettext(printforhelp)); | |
52 | } | |
53 | ||
54 | static void DPKG_ATTR_NORET DPKG_ATTR_PRINTF(3) | |
55 | config_error(const char *file_name, int line_num, const char *fmt, ...) | |
56 | { | |
57 | char *buf = NULL; | |
58 | va_list args; | |
59 | ||
60 | va_start(args, fmt); | |
61 | m_vasprintf(&buf, fmt, args); | |
62 | va_end(args); | |
63 | ||
64 | ohshit(_("configuration error: %s:%d: %s"), file_name, line_num, buf); | |
65 | } | |
66 | ||
67 | static void | |
68 | dpkg_options_load_file(const char *fn, const struct cmdinfo *cmdinfos) | |
69 | { | |
70 | FILE *file; | |
71 | int line_num = 0; | |
72 | char linebuf[MAX_CONFIG_LINE]; | |
73 | ||
74 | file= fopen(fn, "r"); | |
75 | if (!file) { | |
76 | if (errno==ENOENT) | |
77 | return; | |
78 | warning(_("failed to open configuration file '%.255s' for reading: %s"), | |
79 | fn, strerror(errno)); | |
80 | return; | |
81 | } | |
82 | ||
83 | while (fgets(linebuf, sizeof(linebuf), file)) { | |
84 | char *opt; | |
85 | const struct cmdinfo *cip; | |
86 | int l; | |
87 | ||
88 | line_num++; | |
89 | ||
90 | l = strlen(linebuf); | |
91 | while (l && c_isspace(linebuf[l - 1])) | |
92 | l--; | |
93 | linebuf[l] = '\0'; | |
94 | ||
95 | if ((linebuf[0] == '#') || (linebuf[0] == '\0')) | |
96 | continue; | |
97 | for (opt = linebuf; c_isalnum(*opt) || *opt == '-'; opt++) ; | |
98 | if (*opt == '\0') | |
99 | opt=NULL; | |
100 | else { | |
101 | *opt++ = '\0'; | |
102 | if (*opt=='=') opt++; | |
103 | while (c_isspace(*opt)) | |
104 | opt++; | |
105 | ||
106 | opt = str_strip_quotes(opt); | |
107 | if (opt == NULL) | |
108 | config_error(fn, line_num, _("unbalanced quotes in '%s'"), linebuf); | |
109 | } | |
110 | ||
111 | for (cip=cmdinfos; cip->olong || cip->oshort; cip++) { | |
112 | if (!cip->olong) continue; | |
113 | if (strcmp(cip->olong, linebuf) == 0) | |
114 | break; | |
115 | l=strlen(cip->olong); | |
116 | if ((cip->takesvalue==2) && (linebuf[l]=='-') && | |
117 | !opt && strncmp(linebuf, cip->olong, l) == 0) { | |
118 | opt=linebuf+l+1; | |
119 | break; | |
120 | } | |
121 | } | |
122 | ||
123 | if (!cip->olong) | |
124 | config_error(fn, line_num, _("unknown option '%s'"), linebuf); | |
125 | ||
126 | if (cip->takesvalue) { | |
127 | if (!opt) | |
128 | config_error(fn, line_num, _("'%s' needs a value"), linebuf); | |
129 | if (cip->call) cip->call(cip,opt); | |
130 | else | |
131 | *cip->sassignto = m_strdup(opt); | |
132 | } else { | |
133 | if (opt) | |
134 | config_error(fn, line_num, _("'%s' does not take a value"), linebuf); | |
135 | if (cip->call) cip->call(cip,NULL); | |
136 | else | |
137 | *cip->iassignto = cip->arg_int; | |
138 | } | |
139 | } | |
140 | if (ferror(file)) | |
141 | ohshite(_("read error in configuration file '%.255s'"), fn); | |
142 | if (fclose(file)) | |
143 | ohshite(_("error closing configuration file '%.255s'"), fn); | |
144 | } | |
145 | ||
146 | static int | |
147 | valid_config_filename(const struct dirent *dent) | |
148 | { | |
149 | const char *c; | |
150 | ||
151 | if (dent->d_name[0] == '.') | |
152 | return 0; | |
153 | ||
154 | for (c = dent->d_name; *c; c++) | |
155 | if (!c_isalnum(*c) && *c != '_' && *c != '-') | |
156 | return 0; | |
157 | ||
158 | if (*c == '\0') | |
159 | return 1; | |
160 | else | |
161 | return 0; | |
162 | } | |
163 | ||
164 | static void | |
165 | dpkg_options_load_dir(const char *prog, const struct cmdinfo *cmdinfos) | |
166 | { | |
167 | char *dirname; | |
168 | struct dirent **dlist; | |
169 | int dlist_n, i; | |
170 | ||
171 | dirname = str_fmt("%s/%s.cfg.d", CONFIGDIR, prog); | |
172 | ||
173 | dlist_n = scandir(dirname, &dlist, valid_config_filename, alphasort); | |
174 | if (dlist_n < 0) { | |
175 | if (errno == ENOENT) { | |
176 | free(dirname); | |
177 | return; | |
178 | } else | |
179 | ohshite(_("error opening configuration directory '%s'"), dirname); | |
180 | } | |
181 | ||
182 | for (i = 0; i < dlist_n; i++) { | |
183 | char *filename; | |
184 | ||
185 | filename = str_fmt("%s/%s", dirname, dlist[i]->d_name); | |
186 | dpkg_options_load_file(filename, cmdinfos); | |
187 | ||
188 | free(dlist[i]); | |
189 | free(filename); | |
190 | } | |
191 | ||
192 | free(dirname); | |
193 | free(dlist); | |
194 | } | |
195 | ||
196 | void | |
197 | dpkg_options_load(const char *prog, const struct cmdinfo *cmdinfos) | |
198 | { | |
199 | char *home, *file; | |
200 | ||
201 | dpkg_options_load_dir(prog, cmdinfos); | |
202 | ||
203 | file = str_fmt("%s/%s.cfg", CONFIGDIR, prog); | |
204 | dpkg_options_load_file(file, cmdinfos); | |
205 | free(file); | |
206 | ||
207 | home = getenv("HOME"); | |
208 | if (home != NULL) { | |
209 | file = str_fmt("%s/.%s.cfg", home, prog); | |
210 | dpkg_options_load_file(file, cmdinfos); | |
211 | free(file); | |
212 | } | |
213 | } | |
214 | ||
215 | void | |
216 | dpkg_options_parse(const char *const **argvp, const struct cmdinfo *cmdinfos, | |
217 | const char *help_str) | |
218 | { | |
219 | const struct cmdinfo *cip; | |
220 | const char *p, *value; | |
221 | int l; | |
222 | ||
223 | printforhelp = help_str; | |
224 | ||
225 | ++(*argvp); | |
226 | while ((p = **argvp) && p[0] == '-' && p[1] != '\0') { | |
227 | ++(*argvp); | |
228 | if (strcmp(p, "--") == 0) | |
229 | break; | |
230 | if (*++p == '-') { | |
231 | ++p; value=NULL; | |
232 | for (cip= cmdinfos; | |
233 | cip->olong || cip->oshort; | |
234 | cip++) { | |
235 | if (!cip->olong) continue; | |
236 | if (strcmp(p, cip->olong) == 0) | |
237 | break; | |
238 | l= strlen(cip->olong); | |
239 | if (strncmp(p, cip->olong, l) == 0 && | |
240 | (p[l]== ((cip->takesvalue==2) ? '-' : '='))) { value=p+l+1; break; } | |
241 | } | |
242 | if (!cip->olong) badusage(_("unknown option --%s"),p); | |
243 | if (cip->takesvalue) { | |
244 | if (!value) { | |
245 | value= *(*argvp)++; | |
246 | if (!value) badusage(_("--%s option takes a value"),cip->olong); | |
247 | } | |
248 | if (cip->call) cip->call(cip,value); | |
249 | else *cip->sassignto= value; | |
250 | } else { | |
251 | if (value) badusage(_("--%s option does not take a value"),cip->olong); | |
252 | if (cip->call) cip->call(cip,NULL); | |
253 | else | |
254 | *cip->iassignto = cip->arg_int; | |
255 | } | |
256 | } else { | |
257 | while (*p) { | |
258 | for (cip= cmdinfos; (cip->olong || cip->oshort) && *p != cip->oshort; cip++); | |
259 | if (!cip->oshort) badusage(_("unknown option -%c"),*p); | |
260 | p++; | |
261 | if (cip->takesvalue) { | |
262 | if (!*p) { | |
263 | value= *(*argvp)++; | |
264 | if (!value) badusage(_("-%c option takes a value"),cip->oshort); | |
265 | } else { | |
266 | value= p; p=""; | |
267 | if (*value == '=') value++; | |
268 | } | |
269 | if (cip->call) cip->call(cip,value); | |
270 | else *cip->sassignto= value; | |
271 | } else { | |
272 | if (*p == '=') badusage(_("-%c option does not take a value"),cip->oshort); | |
273 | if (cip->call) cip->call(cip,NULL); | |
274 | else | |
275 | *cip->iassignto = cip->arg_int; | |
276 | } | |
277 | } | |
278 | } | |
279 | } | |
280 | } | |
281 | ||
282 | long | |
283 | dpkg_options_parse_arg_int(const struct cmdinfo *cmd, const char *str) | |
284 | { | |
285 | long value; | |
286 | char *end; | |
287 | ||
288 | errno = 0; | |
289 | value = strtol(str, &end, 0); | |
290 | if (str == end || *end || value < 0 || value > INT_MAX || errno != 0) { | |
291 | if (cmd->olong) | |
292 | badusage(_("invalid integer for --%s: '%.250s'"), cmd->olong, str); | |
293 | else | |
294 | badusage(_("invalid integer for -%c: '%.250s'"), cmd->oshort, str); | |
295 | } | |
296 | ||
297 | return value; | |
298 | } | |
299 | ||
300 | void | |
301 | setobsolete(const struct cmdinfo *cip, const char *value) | |
302 | { | |
303 | warning(_("obsolete option '--%s'"), cip->olong); | |
304 | } | |
305 | ||
306 | const struct cmdinfo *cipaction = NULL; | |
307 | ||
308 | /* XXX: This function is a hack. */ | |
309 | static inline int | |
310 | option_short(int c) | |
311 | { | |
312 | return c ? c : '\b'; | |
313 | } | |
314 | ||
315 | void | |
316 | setaction(const struct cmdinfo *cip, const char *value) | |
317 | { | |
318 | if (cipaction && cip) | |
319 | badusage(_("conflicting actions -%c (--%s) and -%c (--%s)"), | |
320 | option_short(cip->oshort), cip->olong, | |
321 | option_short(cipaction->oshort), cipaction->olong); | |
322 | cipaction = cip; | |
323 | } |