/[gentoo-projects]/portage-utils/qfile.c
Gentoo

Contents of /portage-utils/qfile.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.65 - (show annotations) (download) (as text)
Sat Nov 10 06:44:05 2012 UTC (17 months, 1 week ago) by vapier
Branch: MAIN
Changes since 1.64: +12 -12 lines
File MIME type: text/x-csrc
qfile: re-order options and clarify --exclude/--exact are only for --orphans #441696

1 /*
2 * Copyright 2005-2010 Gentoo Foundation
3 * Distributed under the terms of the GNU General Public License v2
4 * $Header: /var/cvsroot/gentoo-projects/portage-utils/qfile.c,v 1.64 2012/10/28 09:44:24 vapier Exp $
5 *
6 * Copyright 2005-2010 Ned Ludd - <solar@gentoo.org>
7 * Copyright 2005-2010 Mike Frysinger - <vapier@gentoo.org>
8 */
9
10 #ifdef APPLET_qfile
11
12 #define QFILE_MAX_MAX_ARGS 5000000
13 #define QFILE_DEFAULT_MAX_ARGS 5000
14 #define QFILE_DEFAULT_MAX_ARGS_STR "5000"
15
16 #define QFILE_FLAGS "ef:m:oRx:S" COMMON_FLAGS
17 static struct option const qfile_long_opts[] = {
18 {"slots", no_argument, NULL, 'S'},
19 {"root-prefix", no_argument, NULL, 'R'},
20 {"from", a_argument, NULL, 'f'},
21 {"max-args", a_argument, NULL, 'm'},
22 {"orphans", no_argument, NULL, 'o'},
23 {"exclude", a_argument, NULL, 'x'},
24 {"exact", no_argument, NULL, 'e'},
25 COMMON_LONG_OPTS
26 };
27 static const char * const qfile_opts_help[] = {
28 "Display installed packages with slots",
29 "Assume arguments are already prefixed by $ROOT",
30 "Read arguments from file <arg> (\"-\" for stdin)",
31 "Treat from file arguments by groups of <arg> (defaults to " QFILE_DEFAULT_MAX_ARGS_STR ")",
32 "List orphan files",
33 "Don't look in package <arg> (used with --orphans)",
34 "Exact match (used with --exclude)",
35 COMMON_OPTS_HELP
36 };
37 static const char qfile_rcsid[] = "$Id: qfile.c,v 1.64 2012/10/28 09:44:24 vapier Exp $";
38 #define qfile_usage(ret) usage(ret, QFILE_FLAGS, qfile_long_opts, qfile_opts_help, lookup_applet_idx("qfile"))
39
40 #define qfile_is_prefix(path, prefix, prefix_length) \
41 (!prefix_length \
42 || (strlen(path) >= (size_t)prefix_length \
43 && (path[prefix_length] == '/' || path[prefix_length] == '\0') \
44 && !strncmp(path, prefix, prefix_length)))
45
46 typedef struct {
47 int length;
48 char **basenames;
49 char **dirnames;
50 char **realdirnames;
51 short *non_orphans;
52 } qfile_args_t;
53
54 struct qfile_opt_state {
55 char *buf;
56 size_t buflen;
57 qfile_args_t args;
58 char *root;
59 char *pwd;
60 char *real_root;
61 size_t real_root_len;
62 char *exclude_pkg;
63 char *exclude_slot;
64 depend_atom *exclude_atom;
65 bool slotted;
66 bool exact;
67 bool orphans;
68 bool assume_root_prefix;
69 };
70
71 /*
72 * We assume the people calling us have chdir(/var/db/pkg) and so
73 * we use relative paths throughout here.
74 */
75 _q_static int qfile_cb(q_vdb_pkg_ctx *pkg_ctx, void *priv)
76 {
77 struct qfile_opt_state *state = priv;
78 const char *catname = pkg_ctx->cat_ctx->name;
79 const char *pkgname = pkg_ctx->name;
80 qfile_args_t *args = &state->args;
81 FILE *fp;
82 const char *base;
83 char pkg[_Q_PATH_MAX];
84 depend_atom *atom = NULL;
85 int i;
86 bool path_ok;
87 char *real_root = state->real_root;
88 char **base_names = args->basenames;
89 char **dir_names = args->dirnames;
90 char **real_dir_names = args->realdirnames;
91 short *non_orphans = args->non_orphans;
92 int found = 0;
93
94 snprintf(pkg, sizeof(pkg), "%s/%s", catname, pkgname);
95
96 /* If exclude_pkg is not NULL, check it. We are looking for files
97 * collisions, and must exclude one package.
98 */
99 if (state->exclude_pkg) {
100 /* see if CATEGORY matches */
101 if (state->exclude_atom->CATEGORY &&
102 strcmp(state->exclude_atom->CATEGORY, catname))
103 goto dont_skip_pkg;
104 atom = atom_explode(pkg);
105 if (state->exclude_atom->PVR) {
106 /* see if PVR is exact match */
107 if (strcmp(state->exclude_atom->PVR, atom->PVR))
108 goto dont_skip_pkg;
109 } else {
110 /* see if PN is exact match */
111 if (strcmp(state->exclude_atom->PN, atom->PN))
112 goto dont_skip_pkg;
113 }
114 if (state->exclude_slot == NULL)
115 goto qlist_done; /* "(CAT/)?(PN|PF)" matches, and no SLOT specified */
116 eat_file_at(pkg_ctx->fd, "SLOT", state->buf, state->buflen);
117 rmspace(state->buf);
118 if (strcmp(state->exclude_slot, state->buf) == 0)
119 goto qlist_done; /* "(CAT/)?(PN|PF):SLOT" matches */
120 }
121 dont_skip_pkg: /* End of the package exclusion tests. */
122
123 fp = q_vdb_pkg_fopenat_ro(pkg_ctx, "CONTENTS");
124 if (fp == NULL)
125 goto qlist_done;
126
127 while (getline(&state->buf, &state->buflen, fp) != -1) {
128 contents_entry *e;
129 e = contents_parse_line(state->buf);
130 if (!e)
131 continue;
132
133 /* assume sane basename() -- doesnt modify argument */
134 if ((base = basename(e->name)) == NULL)
135 continue;
136
137 for (i = 0; i < args->length; i++) {
138 if (base_names[i] == NULL)
139 continue;
140 if (non_orphans && non_orphans[i])
141 continue;
142
143 /* For optimization of qfile(), we also give it an array of the first char
144 * of each basename. This way we avoid numerous strcmp() calls.
145 */
146 if (base[0] != base_names[i][0] || strcmp(base, base_names[i]))
147 continue;
148
149 path_ok = false;
150
151 /* check the full filepath ... */
152 size_t dirname_len = (base - e->name - 1);
153 /* basename(/usr) = usr, dirname(/usr) = /
154 * basename(/usr/bin) = bin, dirname(/usr/bin) = /usr
155 */
156 if (dirname_len == 0)
157 dirname_len = 1;
158
159 if (dir_names[i] &&
160 strncmp(e->name, dir_names[i], dirname_len) == 0 &&
161 dir_names[i][dirname_len] == '\0') {
162 /* dir_name == dirname(CONTENTS) */
163 path_ok = true;
164
165 } else if (real_dir_names[i] &&
166 strncmp(e->name, real_dir_names[i], dirname_len) == 0 &&
167 real_dir_names[i][dirname_len] == '\0') {
168 /* real_dir_name == dirname(CONTENTS) */
169 path_ok = true;
170
171 } else if (real_root[0]) {
172 char rpath[_Q_PATH_MAX + 1], *_rpath;
173 char *fullpath;
174 size_t real_root_len = state->real_root_len;
175
176 xasprintf(&fullpath, "%s%s", real_root, e->name);
177 fullpath[real_root_len + dirname_len] = '\0';
178 _rpath = rpath + real_root_len;
179 if (realpath(fullpath, rpath) == NULL) {
180 if (verbose) {
181 warnp("Could not read real path of \"%s\" (from %s)", fullpath, pkg);
182 warn("We'll never know whether \"%s\" was a result for your query...",
183 e->name);
184 }
185 } else if (!qfile_is_prefix(rpath, real_root, real_root_len)) {
186 if (verbose)
187 warn("Real path of \"%s\" is not under ROOT: %s", fullpath, rpath);
188 } else if (dir_names[i] &&
189 strcmp(_rpath, dir_names[i]) == 0) {
190 /* dir_name == realpath(dirname(CONTENTS)) */
191 path_ok = true;
192 } else if (real_dir_names[i] &&
193 strcmp(_rpath, real_dir_names[i]) == 0) {
194 /* real_dir_name == realpath(dirname(CONTENTS)) */
195 path_ok = true;
196 }
197 free(fullpath);
198 } else if (state->pwd) {
199 if (!strncmp(e->name, state->pwd, dirname_len))
200 path_ok = true;
201 }
202 if (!path_ok)
203 continue;
204
205 if (non_orphans == NULL) {
206 char slot[126];
207
208 if (!atom) {
209 if ((atom = atom_explode(pkg)) == NULL) {
210 warn("invalid atom %s", pkg);
211 continue;
212 }
213 }
214 if (state->slotted) {
215 eat_file_at(pkg_ctx->fd, "SLOT", slot+1, sizeof(slot)-1);
216 rmspace(slot+1);
217 slot[0] = ':';
218 } else
219 slot[0] = '\0';
220 printf("%s%s/%s%s%s%s", BOLD, atom->CATEGORY, BLUE,
221 (state->exact ? pkg_ctx->name : atom->PN),
222 slot, NORM);
223 if (quiet)
224 puts("");
225 else
226 printf(" (%s%s)\n", state->root ? : "", e->name);
227
228 } else {
229 non_orphans[i] = 1;
230 }
231 found++;
232 }
233 }
234 fclose(fp);
235
236 qlist_done:
237 if (atom)
238 atom_implode(atom);
239
240 return found;
241 }
242
243 _q_static void destroy_qfile_args(qfile_args_t *qfile_args)
244 {
245 int i;
246
247 for (i = 0; i < qfile_args->length; ++i) {
248 if (qfile_args->basenames)
249 free(qfile_args->basenames[i]);
250 if (qfile_args->dirnames)
251 free(qfile_args->dirnames[i]);
252 if (qfile_args->realdirnames)
253 free(qfile_args->realdirnames[i]);
254 }
255
256 free(qfile_args->basenames);
257 free(qfile_args->dirnames);
258 free(qfile_args->realdirnames);
259 free(qfile_args->non_orphans);
260
261 memset(qfile_args, 0, sizeof(qfile_args_t));
262 }
263
264 _q_static int
265 prepare_qfile_args(const int argc, const char **argv, struct qfile_opt_state *state)
266 {
267 qfile_args_t *args = &state->args;
268 int i;
269 int nb_of_queries = argc;
270 char *pwd = state->pwd;
271 size_t real_root_len = state->real_root_len;
272 char *real_root = state->real_root;
273 char **basenames = NULL;
274 char **dirnames = NULL;
275 char **realdirnames = NULL;
276 char tmppath[_Q_PATH_MAX+1];
277 char abspath[_Q_PATH_MAX+1];
278
279 /* For each argument, we store its basename, its absolute dirname,
280 * and the realpath of its dirname. Dirnames and their realpaths
281 * are stored without their $ROOT prefix, but $ROOT is used when
282 * checking realpaths.
283 */
284 basenames = xcalloc(argc, sizeof(char*));
285 dirnames = xcalloc(argc, sizeof(char*));
286 realdirnames = xcalloc(argc, sizeof(char*));
287
288 for (i = 0; i < argc; ++i) {
289 /* Record basename, but if it is ".", ".." or "/" */
290 strncpy(abspath, argv[i], _Q_PATH_MAX); /* strncopy so that "argv" can be "const" */
291 strncpy(tmppath, basename(abspath), _Q_PATH_MAX);
292 if ((strlen(tmppath) > 2) ||
293 (strncmp(tmppath, "..", strlen(tmppath))
294 && strncmp(tmppath, "/", strlen(tmppath))))
295 {
296 basenames[i] = xstrdup(tmppath);
297 /* If there is no "/" in the argument, then it's over.
298 * (we are searching a simple file name)
299 */
300 if (strchr(argv[i], '/') == NULL)
301 continue;
302 }
303
304 /* Make sure we have an absolute path available (with "realpath(ROOT)" prefix) */
305 if (argv[i][0] == '/') {
306 if (state->assume_root_prefix)
307 strncpy(abspath, argv[i], _Q_PATH_MAX);
308 else
309 snprintf(abspath, _Q_PATH_MAX, "%s%s", real_root, argv[i]);
310 } else if (pwd) {
311 if (state->assume_root_prefix)
312 snprintf(abspath, _Q_PATH_MAX, "%s/%s", pwd, argv[i]);
313 else
314 snprintf(abspath, _Q_PATH_MAX, "%s%s/%s", real_root, pwd, argv[i]);
315 } else {
316 warn("$PWD was not found in environment, or is not an absolute path");
317 goto skip_query_item;
318 }
319
320 if (basenames[i]) {
321 /* Get both the dirname and its realpath. This paths will
322 * have no trailing slash, but if it is the only char (ie.,
323 * when searching for "/foobar").
324 */
325 strncpy(tmppath, abspath, _Q_PATH_MAX);
326 strncpy(abspath, dirname(tmppath), _Q_PATH_MAX);
327 if (abspath[real_root_len] == '\0')
328 strncat(abspath, "/", 1);
329 dirnames[i] = xstrdup(abspath + real_root_len);
330 if (realpath(abspath, tmppath) == NULL) {
331 if (verbose) {
332 warnp("Could not read real path of \"%s\"", abspath);
333 warn("Results for query item \"%s\" may be inaccurate.", argv[i]);
334 }
335 continue;
336 }
337 if (!qfile_is_prefix(tmppath, real_root, real_root_len)) {
338 warn("Real path of \"%s\" is not under ROOT: %s", abspath, tmppath);
339 goto skip_query_item;
340 }
341 if (tmppath[real_root_len] == '\0')
342 strncat(tmppath, "/", 1);
343 if (strcmp(dirnames[i], tmppath + real_root_len))
344 realdirnames[i] = xstrdup(tmppath + real_root_len);
345 } else {
346 /* No basename means we are looking for something like "/foo/bar/.."
347 * Dirname is meaningless here, we can only get realpath of the full
348 * path and then split it.
349 */
350 if (realpath(abspath, tmppath) == NULL) {
351 warnp("Could not read real path of \"%s\"", abspath);
352 goto skip_query_item;
353 }
354 if (!qfile_is_prefix(tmppath, real_root, real_root_len)) {
355 warn("Real path of \"%s\" is not under ROOT: %s", abspath, tmppath);
356 goto skip_query_item;
357 }
358 strncpy(abspath, tmppath, _Q_PATH_MAX);
359 basenames[i] = xstrdup(basename(abspath));
360 strncpy(abspath, dirname(tmppath), _Q_PATH_MAX);
361 if (tmppath[real_root_len] == '\0')
362 strncat(tmppath, "/", 1);
363 realdirnames[i] = xstrdup(abspath + real_root_len);
364 }
365 continue;
366
367 skip_query_item:
368 --nb_of_queries;
369 warn("Skipping query item \"%s\".", argv[i]);
370 free(basenames[i]);
371 free(dirnames[i]);
372 free(realdirnames[i]);
373 basenames[i] = dirnames[i] = realdirnames[i] = NULL;
374 }
375
376 args->basenames = basenames;
377 args->dirnames = dirnames;
378 args->realdirnames = realdirnames;
379 args->length = argc;
380
381 if (state->orphans)
382 args->non_orphans = xcalloc(argc, sizeof(short));
383
384 return nb_of_queries;
385 }
386
387 int qfile_main(int argc, char **argv)
388 {
389 struct qfile_opt_state state = {
390 .buflen = _Q_PATH_MAX,
391 .slotted = false,
392 .exact = false,
393 .orphans = false,
394 .assume_root_prefix = false,
395 };
396 int i, nb_of_queries, found = 0;
397 char *p;
398 int qargc = 0;
399 char **qargv = NULL;
400 FILE *args_file = NULL;
401 int max_args = QFILE_DEFAULT_MAX_ARGS;
402
403 DBG("argc=%d argv[0]=%s argv[1]=%s",
404 argc, argv[0], argc > 1 ? argv[1] : "NULL?");
405
406 while ((i = GETOPT_LONG(QFILE, qfile, "")) != -1) {
407 switch (i) {
408 COMMON_GETOPTS_CASES(qfile)
409 case 'S': state.slotted = true; break;
410 case 'e': state.exact = true; break;
411 case 'f':
412 if (args_file)
413 err("Don't use -f twice!");
414 if (strcmp(optarg, "-") == 0)
415 args_file = stdin;
416 else if ((args_file = fopen(optarg, "r")) == NULL) {
417 warnp("%s", optarg);
418 goto exit;
419 }
420 break;
421 case 'm':
422 errno = 0;
423 max_args = strtol(optarg, &p, 10);
424 if (errno != 0) {
425 warnp("%s: not a valid integer", optarg);
426 goto exit;
427 } else if (p == optarg || *p != '\0') {
428 warn("%s: not a valid integer", optarg);
429 goto exit;
430 }
431 if (max_args <= 0 || max_args > QFILE_MAX_MAX_ARGS) {
432 warn("%s: silly value!", optarg);
433 goto exit;
434 }
435 break;
436 case 'o': state.orphans = true; break;
437 case 'R': state.assume_root_prefix = true; break;
438 case 'x':
439 if (state.exclude_pkg)
440 err("--exclude can only be used once.");
441 state.exclude_pkg = xstrdup(optarg);
442 if ((state.exclude_slot = strchr(state.exclude_pkg, ':')) != NULL)
443 *state.exclude_slot++ = '\0';
444 state.exclude_atom = atom_explode(optarg);
445 if (!state.exclude_atom)
446 err("invalid atom %s", optarg);
447 break;
448 }
449 }
450 if (!state.exact && verbose)
451 state.exact = true;
452 if ((argc == optind) && (args_file == NULL))
453 qfile_usage(EXIT_FAILURE);
454
455 if ((args_file == NULL) && (max_args != QFILE_DEFAULT_MAX_ARGS))
456 warn("--max-args is only used when reading arguments from a file (with -f)");
457
458 /* Are we using --from ? */
459 if (args_file == NULL) {
460 qargc = argc - optind;
461 qargv = argv + optind;
462 } else {
463 qargc = 0;
464 qargv = xcalloc(max_args, sizeof(char*));
465 }
466
467 state.buf = xmalloc(state.buflen);
468 if (state.assume_root_prefix) {
469 /* Get a copy of $ROOT, with no trailing slash
470 * (this one is just for qfile(...) output)
471 */
472 size_t lastc = strlen(portroot) - 1;
473 state.root = xstrdup(portroot);
474 if (state.root[lastc] == '/')
475 state.root[lastc] = '\0';
476 }
477
478 /* Try to get $PWD. Must be absolute, with no trailing slash. */
479 state.pwd = getcwd(state.buf, state.buflen);
480 if (state.pwd) {
481 size_t lastc = strlen(state.pwd) - 1;
482 state.pwd = xstrdup(state.pwd);
483 if (state.pwd[lastc] == '/')
484 state.pwd[lastc] = '\0';
485 }
486
487 /* Get realpath of $ROOT, with no trailing slash */
488 if (portroot[0] == '/')
489 p = realpath(portroot, NULL);
490 else if (state.pwd) {
491 snprintf(state.buf, state.buflen, "%s/%s", state.pwd, portroot);
492 p = realpath(state.buf, NULL);
493 } else
494 p = NULL;
495 if (p == NULL)
496 errp("Could not read real path of ROOT (\"%s\") + $PWD", portroot);
497 if (!strcmp(p, "/"))
498 *p = '\0';
499 state.real_root = p;
500 state.real_root_len = strlen(p);
501
502 do { /* This block may be repeated if using --from with a big files list */
503 if (args_file) {
504 /* Read up to max_args files from the input file */
505 for (i = 0; i < qargc; ++i)
506 free(qargv[i]);
507 qargc = 0;
508 while (getline(&state.buf, &state.buflen, args_file) != -1) {
509 if ((p = strchr(state.buf, '\n')) != NULL)
510 *p = '\0';
511 if (state.buf == p)
512 continue;
513 qargv[qargc] = xstrdup(state.buf);
514 if (++qargc >= max_args)
515 break;
516 }
517 }
518 if (qargc == 0)
519 break;
520
521 /* Prepare the qfile(...) arguments structure */
522 nb_of_queries = prepare_qfile_args(qargc, (const char **) qargv, &state);
523 if (nb_of_queries < 0)
524 break;
525
526 /* Now do the actual `qfile` checking */
527 if (nb_of_queries)
528 found += q_vdb_foreach_pkg(qfile_cb, &state, NULL);
529
530 if (state.args.non_orphans) {
531 /* display orphan files */
532 for (i = 0; i < state.args.length; i++) {
533 if (state.args.non_orphans[i])
534 continue;
535 if (state.args.basenames[i]) {
536 found = 0; /* ~inverse return code (as soon as an orphan is found, return non-zero) */
537 if (!quiet)
538 puts(qargv[i]);
539 else
540 break;
541 }
542 }
543 }
544
545 destroy_qfile_args(&state.args);
546 } while (args_file && qargc == max_args);
547
548 exit:
549 if (args_file) {
550 if (qargv) {
551 for (i = 0; i < qargc; ++i)
552 free(qargv[i]);
553 free(qargv);
554 }
555
556 if (args_file != stdin)
557 fclose(args_file);
558 }
559
560 destroy_qfile_args(&state.args);
561 free(state.buf);
562 free(state.root);
563 free(state.real_root);
564 free(state.pwd);
565 if (state.exclude_pkg) {
566 free(state.exclude_pkg);
567 /* don't free state.exclude_slot as it's a pointer into exclude_pkg */
568 atom_implode(state.exclude_atom);
569 }
570
571 return (found ? EXIT_SUCCESS : EXIT_FAILURE);
572 }
573
574 #else
575 DEFINE_APPLET_STUB(qfile)
576 #endif

  ViewVC Help
Powered by ViewVC 1.1.20