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

Contents of /portage-utils/qfile.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.66 - (show annotations) (download) (as text)
Mon Apr 29 05:32:43 2013 UTC (19 months, 3 weeks ago) by vapier
Branch: MAIN
Changes since 1.65: +10 -3 lines
File MIME type: text/x-csrc
qfile: add a --basename option so people can match just the base name of a path #441696 by Samuli Suominen

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

  ViewVC Help
Powered by ViewVC 1.1.20