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

Contents of /portage-utils/qmerge.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.130 - (show annotations) (download) (as text)
Thu May 9 05:28:11 2013 UTC (11 months, 2 weeks ago) by vapier
Branch: MAIN
Changes since 1.129: +6 -4 lines
File MIME type: text/x-csrc
qmerge: change output when removing unmodified config files to <<< rather than ***

also abort if unlinking failed such as trying to remove a file but lack perms (e.g. non-root when ROOT=/)

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/qmerge.c,v 1.129 2013/05/03 22:28:48 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_qmerge
11
12 #include <fnmatch.h>
13 #include <glob.h>
14 #include <sys/stat.h>
15 /* This is a GNUlib hack, because GNUlib doesn't provide st_mtim members
16 * of struct stat, but instead provides wrappers to retrieve the time
17 * fields (stat-time module). We just define a macro in case people are
18 * building without autoconf. */
19 #ifdef _GL_SYS_STAT_H
20 # include "stat-time.h"
21 #else
22 # define get_stat_mtime(X) ((X)->st_mtim)
23 #endif
24
25 #ifndef GLOB_BRACE
26 # define GLOB_BRACE (1 << 10) /* Expand "{a,b}" to "a" "b". */
27 #endif
28
29 /*
30 --nofiles don't verify files in package
31 --noscript don't execute pkg_{pre,post}{inst,rm} (if any)
32 */
33
34 /* #define BUSYBOX "/bin/busybox" */
35 #define BUSYBOX ""
36
37 int old_repo = 0;
38
39 #define QMERGE_FLAGS "fFsKUpuyO5" COMMON_FLAGS
40 static struct option const qmerge_long_opts[] = {
41 {"fetch", no_argument, NULL, 'f'},
42 {"force", no_argument, NULL, 'F'},
43 {"search", no_argument, NULL, 's'},
44 {"install", no_argument, NULL, 'K'},
45 {"unmerge", no_argument, NULL, 'U'},
46 {"pretend", no_argument, NULL, 'p'},
47 {"update", no_argument, NULL, 'u'},
48 {"yes", no_argument, NULL, 'y'},
49 {"nodeps", no_argument, NULL, 'O'},
50 {"nomd5", no_argument, NULL, '5'},
51 COMMON_LONG_OPTS
52 };
53
54 static const char * const qmerge_opts_help[] = {
55 "Fetch package and newest Packages metadata",
56 "Fetch package (skipping Packages)",
57 "Search available packages",
58 "Install package",
59 "Uninstall package",
60 "Pretend only",
61 "Update only",
62 "Don't prompt before overwriting",
63 "Don't merge dependencies",
64 "Don't verify MD5 digest of files",
65 COMMON_OPTS_HELP
66 };
67
68 static const char qmerge_rcsid[] = "$Id: qmerge.c,v 1.129 2013/05/03 22:28:48 vapier Exp $";
69 #define qmerge_usage(ret) usage(ret, QMERGE_FLAGS, qmerge_long_opts, qmerge_opts_help, lookup_applet_idx("qmerge"))
70
71 char search_pkgs = 0;
72 char interactive = 1;
73 char install = 0;
74 char uninstall = 0;
75 char force_download = 0;
76 char follow_rdepends = 1;
77 char nomd5 = 0;
78 char qmerge_strict = 0;
79 char update_only = 0;
80 const char Packages[] = "Packages";
81
82 /*
83 "CHOST", "DEPEND", "DESCRIPTION", "EAPI",
84 "IUSE", "KEYWORDS", "LICENSE", "PDEPEND",
85 "PROVIDE", "RDEPEND", "SLOT", "USE"
86 */
87 struct pkg_t {
88 char PF[64];
89 char CATEGORY[64];
90 char DESC[126];
91 char LICENSE[64];
92 char RDEPEND[BUFSIZ];
93 char MD5[34];
94 char SHA1[42];
95 char SLOT[64];
96 size_t SIZE;
97 char USE[BUFSIZ];
98 char REPO[64];
99 } Pkg;
100
101 struct llist_char_t {
102 char *data;
103 struct llist_char_t *next;
104 };
105
106 typedef struct llist_char_t llist_char;
107
108 _q_static void pkg_fetch(int, const depend_atom *, const struct pkg_t *);
109 _q_static void pkg_merge(int, const depend_atom *, const struct pkg_t *);
110 _q_static int pkg_unmerge(const char *, const char *, queue *);
111 _q_static struct pkg_t *grab_binpkg_info(const char *);
112 _q_static char *find_binpkg(const char *);
113
114 _q_static void fetch(const char *destdir, const char *src)
115 {
116 char buf[BUFSIZ];
117
118 if (!binhost[0])
119 errf("PORTAGE_BINHOST= does not appear to be valid");
120
121 fflush(stdout);
122 fflush(stderr);
123
124 #if 0
125 if (getenv("FETCHCOMMAND") != NULL) {
126 snprintf(buf, sizeof(buf), "(export DISTDIR='%s' URI='%s/%s'; %s)",
127 destdir, binhost, src, getenv("FETCHCOMMAND"));
128 xsystem(buf);
129 } else
130 #endif
131 {
132 pid_t p;
133 int status;
134
135 char prog[] = "wget";
136 char argv_c[] = "-c";
137 char argv_P[] = "-P";
138 char argv_q[] = "-q";
139 char *argv_dir = xstrdup(destdir);
140 char *argv[] = {
141 prog,
142 argv_c,
143 argv_P,
144 argv_dir,
145 buf,
146 quiet ? argv_q : NULL,
147 NULL,
148 };
149 if (!(force_download || install) && pretend)
150 strcpy(prog, "echo");
151 snprintf(buf, sizeof(buf), "%s/%s", binhost, src);
152
153 p = vfork();
154 if (p == 0)
155 _exit(execvp(prog, argv));
156
157 free(argv_dir);
158
159 waitpid(p, &status, 0);
160 #if 0
161 if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
162 return;
163 #endif
164 }
165
166 fflush(stdout);
167 fflush(stderr);
168 }
169
170 _q_static void qmerge_initialize(void)
171 {
172 if (strlen(BUSYBOX))
173 if (access(BUSYBOX, X_OK) != 0)
174 err(BUSYBOX " must be installed");
175
176 if (access("/bin/sh", X_OK) != 0)
177 err("/bin/sh must be installed");
178
179 if (pkgdir[0] != '/')
180 errf("PKGDIR='%s' does not appear to be valid", pkgdir);
181
182 if (!search_pkgs && !pretend) {
183 if (mkdir_p(pkgdir, 0755))
184 errp("could not setup PKGDIR: %s", pkgdir);
185 }
186
187 char *buf;
188 xasprintf(&buf, "%s/portage", port_tmpdir);
189 mkdir_p(buf, 0755);
190 xchdir(buf);
191
192 if (force_download != 2) {
193 if (force_download)
194 unlink(Packages);
195
196 if (access(Packages, R_OK) != 0) {
197 xasprintf(&buf, "%s/portage/", port_tmpdir);
198 if (access(Packages, R_OK) != 0)
199 fetch(buf, Packages);
200 free(buf);
201 }
202 }
203 }
204
205 struct qmerge_bv_state {
206 const char *catname;
207 const char *pkgname;
208 char buf[4096];
209 char *retbuf;
210 };
211
212 _q_static int qmerge_filter_cat(q_vdb_cat_ctx *cat_ctx, void *priv)
213 {
214 struct qmerge_bv_state *state = priv;
215 return !state->catname || strcmp(cat_ctx->name, state->catname) == 0;
216 }
217
218 _q_static int qmerge_best_version_cb(q_vdb_pkg_ctx *pkg_ctx, void *priv)
219 {
220 struct qmerge_bv_state *state = priv;
221 if (qlist_match(pkg_ctx, state->buf, NULL, true))
222 snprintf(state->retbuf, sizeof(state->buf), "%s/%s",
223 pkg_ctx->cat_ctx->name, pkg_ctx->name);
224 return 0;
225 }
226
227 _q_static char *best_version(const char *catname, const char *pkgname)
228 {
229 static int vdb_check = 1;
230 static char retbuf[4096];
231 struct qmerge_bv_state state = {
232 .catname = catname,
233 .pkgname = pkgname,
234 .retbuf = retbuf,
235 };
236
237 /* Make sure these dirs exist before we try walking them */
238 switch (vdb_check) {
239 case 1: {
240 int fd = open(portroot, O_RDONLY|O_CLOEXEC);
241 if (fd >= 0) {
242 /* skip leading slash */
243 vdb_check = faccessat(fd, portvdb + 1, X_OK, 0);
244 close(fd);
245 } else
246 vdb_check = -1;
247 }
248 if (vdb_check)
249 case -1:
250 goto done;
251 }
252
253 retbuf[0] = '\0';
254 snprintf(state.buf, sizeof(state.buf), "%s%s%s",
255 catname ? : "", catname ? "/" : "", pkgname);
256 q_vdb_foreach_pkg(qmerge_best_version_cb, &state, qmerge_filter_cat);
257
258 done:
259 return retbuf;
260 }
261
262 _q_static int
263 config_protected(const char *buf, int cp_argc, char **cp_argv,
264 int cpm_argc, char **cpm_argv)
265 {
266 int i;
267 char dest[_Q_PATH_MAX];
268 snprintf(dest, sizeof(dest), "%s%s", portroot, buf);
269
270 /* Check CONFIG_PROTECT_MASK */
271 for (i = 1; i < cpm_argc; ++i)
272 if (strncmp(cpm_argv[i], buf, strlen(cpm_argv[i])) == 0)
273 return 0;
274
275 /* Check CONFIG_PROTECT */
276 for (i = 1; i < cp_argc; ++i)
277 if (strncmp(cp_argv[i], buf, strlen(cp_argv[i])) == 0)
278 if (access(dest, R_OK) == 0)
279 return 1;
280
281 /* this would probably be bad */
282 if (strcmp("/bin/sh", buf) == 0)
283 return 1;
284
285 return 0;
286 }
287
288 _q_static void crossmount_rm(const char *fname, const struct stat st)
289 {
290 struct stat lst;
291
292 assert(pretend == 0);
293
294 if (lstat(fname, &lst) == -1)
295 return;
296 if (lst.st_dev != st.st_dev) {
297 warn("skipping crossmount install masking: %s", fname);
298 return;
299 }
300 qprintf("%s<<<%s %s\n", YELLOW, NORM, fname);
301 rm_rf(fname);
302 }
303
304 void install_mask_pwd(int iargc, char **iargv, const struct stat st);
305 void install_mask_pwd(int iargc, char **iargv, const struct stat st)
306 {
307 char buf[1024];
308 int i;
309
310 for (i = 1; i < iargc; i++) {
311
312 if (iargv[i][0] != '/')
313 continue;
314
315 snprintf(buf, sizeof(buf), ".%s", iargv[i]);
316
317 if ((strchr(iargv[i], '*') != NULL) || (strchr(iargv[i], '{') != NULL)) {
318 int g;
319 glob_t globbuf;
320
321 globbuf.gl_offs = 0;
322 if (glob(buf, GLOB_DOOFFS|GLOB_BRACE, NULL, &globbuf) == 0) {
323 for (g = 0; g < (int)globbuf.gl_pathc; g++) {
324 strncpy(buf, globbuf.gl_pathv[g], sizeof(buf));
325 /* qprintf("globbed: %s\n", globbuf.gl_pathv[g]); */
326 crossmount_rm(globbuf.gl_pathv[g], st);
327 }
328 globfree(&globbuf);
329 }
330 continue;
331 }
332 crossmount_rm(iargv[i], st);
333 }
334 }
335
336 _q_static char *
337 atom2str(const depend_atom *atom, char *buf, size_t size)
338 {
339 if (atom->PR_int)
340 snprintf(buf, size, "%s-%s-r%i", atom->PN, atom->PV, atom->PR_int);
341 else
342 snprintf(buf, size, "%s-%s", atom->PN, atom->PV);
343 return buf;
344 }
345
346 _q_static char
347 qprint_tree_node(int level, const depend_atom *atom, const struct pkg_t *pkg)
348 {
349 char buf[1024];
350 char *p;
351 int i, ret;
352
353 char install_ver[126] = "";
354 char c = 'N';
355
356 if (!pretend)
357 return 0;
358
359 p = best_version(pkg->CATEGORY, atom->PN);
360 if (strlen(p) < 1) {
361 c = 'N';
362 snprintf(buf, sizeof(buf), "%sN%s", GREEN, NORM);
363 } else {
364 depend_atom *subatom = atom_explode(p);
365 if (subatom != NULL) {
366 atom2str(subatom, buf, sizeof(buf));
367 atom2str(atom, install_ver, sizeof(install_ver));
368 ret = atom_compare_str(install_ver, buf);
369 switch (ret) {
370 case EQUAL: c = 'R'; break;
371 case NEWER: c = 'U'; break;
372 case OLDER: c = 'D'; break;
373 default: c = '?'; break;
374 }
375 strncpy(buf, subatom->P, sizeof(buf));
376 snprintf(install_ver, sizeof(install_ver), "[%s%s%s] ", DKBLUE, buf, NORM);
377 atom_implode(subatom);
378 }
379 if (update_only && c != 'U')
380 return c;
381 if ((c == 'R' || c == 'D') && update_only && level)
382 return c;
383 if (c == 'R')
384 snprintf(buf, sizeof(buf), "%s%c%s", YELLOW, c, NORM);
385 if (c == 'U' || c == 'D')
386 snprintf(buf, sizeof(buf), "%s%c%s", BLUE, c, NORM);
387 #if 0
388 if (level) {
389 switch (c) {
390 case 'N':
391 case 'U': break;
392 default:
393 qprintf("[%c] %d %s\n", c, level, pkg->PF); return;
394 break;
395 }
396 }
397 #endif
398 }
399 printf("[%s] ", buf);
400 for (i = 0; i < level; ++i)
401 putchar(' ');
402 if (verbose)
403 printf("%s%s/%s%s %s%s%s%s%s%s\n", DKGREEN, pkg->CATEGORY, pkg->PF, NORM,
404 install_ver, strlen(pkg->USE) > 0 ? "(" : "", RED, pkg->USE, NORM, strlen(pkg->USE) > 0 ? ")" : "");
405 else
406 printf("%s%s/%s%s\n", DKGREEN, pkg->CATEGORY, pkg->PF, NORM);
407 return c;
408 }
409
410 _q_static void
411 pkg_run_func(const char *vdb_path, const char *phases, const char *func, const char *D, const char *T)
412 {
413 const char *phase;
414 char *script;
415
416 /* This assumes no func is a substring of another func.
417 * Today, that assumption is valid for all funcs ...
418 * The phases are the func with the "pkg_" chopped off. */
419 phase = func + 4;
420 if (strstr(phases, phase) == NULL) {
421 qprintf("--- %s\n", func);
422 return;
423 }
424
425 qprintf(">>> %s\n", func);
426
427 xasprintf(&script,
428 /* Provide funcs required by the PMS */
429 "EBUILD_PHASE=%3$s\n"
430 "debug-print() { :; }\n"
431 "debug-print-function() { :; }\n"
432 "debug-print-section() { :; }\n"
433 /* Not quite right */
434 "has_version() { qlist -ICq -e '$1' >/dev/null; }\n"
435 /* best_version() */
436 "use() { useq \"$@\"; }\n"
437 "usex() { useq \"$1\" && echo \"${2-yes}$4\" || echo \"${3-no}$5\"; }\n"
438 "useq() { hasq \"$1\" $USE; }\n"
439 "has() { hasq \"$@\"; }\n"
440 "hasq() { local h=$1; shift; case \" $* \" in *\" $h \"*) return 0;; *) return 1;; esac; }\n"
441 "hasv() { hasq \"$@\" && echo \"$1\"; }\n"
442 "elog() { printf ' * %%b\\n' \"$*\"; }\n"
443 "einfo() { elog \"$@\"; }\n"
444 "ewarn() { elog \"$@\"; }\n"
445 "eqawarn() { elog \"QA: \"\"$@\"; }\n"
446 "eerror() { elog \"$@\"; }\n"
447 "die() { eerror \"$@\"; exit 1; }\n"
448 "ebegin() { printf ' * %%b ...' \"$*\"; }\n"
449 "eend() { local r=${1:-$?}; [ $# -gt 0 ] && shift; [ $r -eq 0 ] && echo ' [ ok ]' || echo \" $* \"'[ !! ]'; return $r; }\n"
450 /* Unpack the env if need be */
451 "[ -e '%1$s/environment' ] || { bzip2 -dc '%1$s/environment.bz2' > '%1$s/environment' || exit 1; }\n"
452 /* Load the main env */
453 ". '%1$s/environment'\n"
454 /* Reload env vars that matter to us */
455 "MERGE_TYPE=binary\n"
456 "ROOT='%4$s'\n"
457 "EROOT=\"${EPREFIX%%/}/${ROOT#/}\"\n"
458 "D='%5$s'\n"
459 "ED=\"${EPREFIX%%/}/${D#/}\"\n"
460 "T='%6$s'\n"
461 /* Finally run the func */
462 "%2$s\n"
463 /* Ignore func return values (not exit values) */
464 ":",
465 vdb_path, func, phase, portroot, D, T);
466 xsystembash(script);
467 free(script);
468 }
469
470 /* Copy one tree (the single package) to another tree (ROOT) */
471 _q_static int
472 merge_tree_at(int fd_src, const char *src, int fd_dst, const char *dst,
473 FILE *contents, queue **objs, char **cpathp, int iargc, char **iargv,
474 int cp_argc, char **cp_argv, int cpm_argc, char **cpm_argv)
475 {
476 int i, ret, subfd_src, subfd_dst;
477 DIR *dir;
478 struct dirent *de;
479 struct stat st;
480 char *cpath;
481 size_t clen, nlen, mnlen;
482
483 ret = -1;
484
485 /* Get handles to these subdirs */
486 subfd_src = openat(fd_src, src, O_RDONLY|O_CLOEXEC);
487 if (subfd_src < 0)
488 return ret;
489 subfd_dst = openat(fd_dst, dst, O_RDONLY|O_CLOEXEC);
490 if (subfd_dst < 0) {
491 close(subfd_src);
492 return ret;
493 }
494
495 dir = fdopendir(subfd_src);
496 if (!dir)
497 goto done;
498
499 cpath = *cpathp;
500 clen = strlen(cpath);
501 cpath[clen] = '/';
502 nlen = mnlen = 0;
503
504 while ((de = readdir(dir)) != NULL) {
505 const char *name = de->d_name;
506
507 if (!strcmp(name, ".") || !strcmp(name, ".."))
508 continue;
509
510 /* Build up the full path for this entry */
511 nlen = strlen(name);
512 if (mnlen < nlen) {
513 cpath = *cpathp = xrealloc(*cpathp, clen + 1 + nlen + 1);
514 mnlen = nlen;
515 }
516 strcpy(cpath + clen + 1, name);
517
518 /* Check INSTALL_MASK */
519 for (i = 1; i < iargc; ++i) {
520 if (fnmatch(iargv[i], cpath, 0) == 0) {
521 unlinkat(subfd_src, name, 0);
522 unlinkat(subfd_dst, name, 0);
523 continue;
524 }
525 }
526
527 /* Find out what the source path is */
528 if (fstatat(subfd_src, name, &st, AT_SYMLINK_NOFOLLOW)) {
529 warnp("could not read %s", cpath);
530 continue;
531 }
532
533 /* Migrate a directory */
534 if (S_ISDIR(st.st_mode)) {
535 if (mkdirat(subfd_dst, name, st.st_mode)) {
536 if (errno != EEXIST) {
537 warnp("could not read %s", cpath);
538 continue;
539 }
540
541 /* XXX: update times of dir ? */
542 }
543
544 #if 0 /* We filter out "dir" as it's generally unnecessary cruft */
545 /* syntax: dir dirname */
546 fprintf(contents, "dir %s\n", cpath);
547 *objs = add_set(cpath, "", *objs);
548 qprintf("%s>>>%s %s%s%s/\n", GREEN, NORM, DKBLUE, cpath, NORM);
549 #endif
550
551 /* Copy all of these contents */
552 merge_tree_at(subfd_src, name, subfd_dst, name, contents, objs, cpathp,
553 iargc, iargv, cp_argc, cp_argv, cpm_argc, cpm_argv);
554 cpath = *cpathp;
555 mnlen = 0;
556
557 /* In case we didn't install anything, prune the empty dir */
558 unlinkat(subfd_dst, name, AT_REMOVEDIR);
559 }
560
561 /* Migrate a file */
562 else if (S_ISREG(st.st_mode)) {
563 struct timespec times[2];
564 int fd_srcf, fd_dstf;
565 unsigned char *hash;
566 const char *tmpname, *dname;
567
568 /* syntax: obj filename hash mtime */
569 hash = hash_file_at(subfd_src, name, HASH_MD5);
570 fprintf(contents, "obj %s %s %"PRIu64"u\n", cpath, hash, (uint64_t)st.st_mtime);
571
572 /* Check CONFIG_PROTECT */
573 if (config_protected(cpath, cp_argc, cp_argv, cpm_argc, cpm_argv)) {
574 /* ._cfg####_ */
575 char *num;
576 dname = num = alloca(nlen + 10 + 1);
577 *num++ = '.';
578 *num++ = '_';
579 *num++ = 'c';
580 *num++ = 'f';
581 *num++ = 'g';
582 strcpy(num + 5, name);
583 for (i = 0; i < 10000; ++i) {
584 sprintf(num, "%04i", i);
585 num[4] = '_';
586 if (faccessat(subfd_dst, dname, F_OK, AT_SYMLINK_NOFOLLOW))
587 break;
588 }
589 qprintf("%s>>>%s %s (%s)\n", GREEN, NORM, cpath, dname);
590 } else {
591 dname = name;
592 qprintf("%s>>>%s %s\n", GREEN, NORM, cpath);
593 }
594 *objs = add_set(cpath, "", *objs);
595
596 /* First try fast path -- src/dst are same device */
597 if (renameat(subfd_src, dname, subfd_dst, name) == 0)
598 continue;
599
600 /* Fall back to slow path -- manual read/write */
601 fd_srcf = openat(subfd_src, name, O_RDONLY|O_CLOEXEC);
602 if (fd_srcf < 0) {
603 warnp("could not read %s", cpath);
604 continue;
605 }
606
607 /* Do not write the file in place ...
608 * will fail with files that are in use.
609 * XXX: Should we make this random ?
610 */
611 tmpname = ".qmerge.update";
612 fd_dstf = openat(subfd_dst, tmpname, O_WRONLY|O_CLOEXEC|O_CREAT|O_TRUNC, st.st_mode);
613 if (fd_dstf < 0) {
614 warnp("could not write %s", cpath);
615 close(fd_srcf);
616 continue;
617 }
618
619 /* Make sure owner/mode is sane before we write out data */
620 if (fchown(fd_dstf, st.st_uid, st.st_gid)) {
621 warnp("could not set ownership %s", cpath);
622 continue;
623 }
624 if (fchmod(fd_dstf, st.st_mode)) {
625 warnp("could not set permission %s", cpath);
626 continue;
627 }
628
629 /* Do the actual data copy */
630 if (copy_file_fd(fd_srcf, fd_dstf)) {
631 warnp("could not write %s", cpath);
632 if (ftruncate(fd_dstf, 0)) {
633 /* don't care */;
634 }
635 close(fd_srcf);
636 close(fd_dstf);
637 continue;
638 }
639
640 /* Preserve the file times */
641 times[0] = get_stat_mtime(&st);
642 times[1] = get_stat_mtime(&st);
643 futimens(fd_dstf, times);
644
645 close(fd_srcf);
646 close(fd_dstf);
647
648 /* Move the new tmp dst file to the right place */
649 if (renameat(subfd_dst, tmpname, subfd_dst, dname)) {
650 warnp("could not rename %s to %s", tmpname, cpath);
651 continue;
652 }
653 }
654
655 /* Migrate a symlink */
656 else if (S_ISLNK(st.st_mode)) {
657 size_t len = st.st_size;
658 char *sym = alloca(len + 1);
659
660 /* Find out what we're pointing to */
661 if (readlinkat(subfd_src, name, sym, len) == -1) {
662 warnp("could not read link %s", cpath);
663 continue;
664 }
665 sym[len] = '\0';
666
667 /* syntax: sym src -> dst mtime */
668 fprintf(contents, "sym %s -> %s %"PRIu64"u\n", cpath, sym, (uint64_t)st.st_mtime);
669 qprintf("%s>>>%s %s%s -> %s%s\n", GREEN, NORM, CYAN, cpath, sym, NORM);
670 *objs = add_set(cpath, "", *objs);
671
672 /* Make it in the dest tree */
673 if (symlinkat(sym, subfd_dst, name)) {
674 /* If the symlink exists, unlink it and try again */
675 if (errno != EEXIST ||
676 unlinkat(subfd_dst, name, 0) ||
677 symlinkat(sym, subfd_dst, name)) {
678 warnp("could not create link %s to %s", cpath, sym);
679 continue;
680 }
681 }
682
683 struct timespec times[2];
684 times[0] = get_stat_mtime(&st);
685 times[1] = get_stat_mtime(&st);
686 utimensat(subfd_dst, name, times, AT_SYMLINK_NOFOLLOW);
687 }
688
689 /* WTF is this !? */
690 else {
691 warnp("unknown file type %s", cpath);
692 continue;
693 }
694 }
695
696 ret = 0;
697
698 done:
699 close(subfd_src);
700 close(subfd_dst);
701
702 return ret;
703 }
704
705 /* Copy one tree (the single package) to another tree (ROOT) */
706 _q_static int
707 merge_tree(const char *src, const char *dst, FILE *contents,
708 queue **objs, int iargc, char **iargv)
709 {
710 int ret;
711 int cp_argc, cpm_argc;
712 char **cp_argv, **cpm_argv;
713 char *cpath;
714
715 /* XXX: be nice to pull this out of the current func
716 * so we don't keep reparsing the same env var
717 * when unmerging multiple packages.
718 */
719 makeargv(config_protect, &cp_argc, &cp_argv);
720 makeargv(config_protect_mask, &cpm_argc, &cpm_argv);
721
722 cpath = xstrdup("");
723 ret = merge_tree_at(AT_FDCWD, src, AT_FDCWD, dst, contents, objs, &cpath,
724 iargc, iargv, cp_argc, cp_argv, cpm_argc, cpm_argv);
725 free(cpath);
726
727 freeargv(cp_argc, cp_argv);
728 freeargv(cpm_argc, cpm_argv);
729
730 return ret;
731 }
732
733 /* oh shit getting into pkg mgt here. FIXME: write a real dep resolver. */
734 _q_static void
735 pkg_merge(int level, const depend_atom *atom, const struct pkg_t *pkg)
736 {
737 queue *objs;
738 FILE *fp, *contents;
739 char buf[1024], phases[128];
740 char *tbz2, *p, *D, *T;
741 int i;
742 char **ARGV;
743 int ARGC;
744 struct stat st;
745 char **iargv;
746 char c;
747 int iargc;
748
749 if (!install || !pkg || !atom)
750 return;
751
752 if (!pkg->PF[0] || !pkg->CATEGORY) {
753 if (verbose) warn("CPF is really NULL at level %d", level);
754 return;
755 }
756
757 c = qprint_tree_node(level, atom, pkg);
758
759 if (0)
760 if (((c == 'R') || (c == 'D')) && update_only)
761 return;
762
763 if (pkg->RDEPEND[0] && follow_rdepends) {
764 const char *rdepend;
765
766 IF_DEBUG(fprintf(stderr, "\n+Parent: %s/%s\n", pkg->CATEGORY, pkg->PF));
767 IF_DEBUG(fprintf(stderr, "+Depstring: %s\n", pkg->RDEPEND));
768
769 /* <hack> */
770 if (strncmp(pkg->RDEPEND, "|| ", 3) == 0) {
771 if (verbose)
772 qfprintf(stderr, "fix this rdepend hack %s\n", pkg->RDEPEND);
773 rdepend = "";
774 } else
775 rdepend = pkg->RDEPEND;
776 /* </hack> */
777
778 makeargv(rdepend, &ARGC, &ARGV);
779 /* Walk the rdepends here. Merging what need be. */
780 for (i = 1; i < ARGC; i++) {
781 depend_atom *subatom, *ratom;
782 char *name = ARGV[i];
783 switch (*name) {
784 case '|':
785 case '!':
786 case '<':
787 case '>':
788 case '=':
789 if (verbose)
790 qfprintf(stderr, "Unhandled depstring %s\n", name);
791 case '\0':
792 break;
793 default:
794 if (*name == '~') {
795 name = ARGV[i] + 1;
796 /* warn("newname = %s", name); */
797 }
798 if ((subatom = atom_explode(name)) != NULL) {
799 char *dep;
800 struct pkg_t *subpkg;
801 char *resolved = NULL;
802
803 dep = find_binpkg(name);
804
805 if (strncmp(name, "virtual/", 8) == 0) {
806 if (virtuals == NULL)
807 virtuals = resolve_virtuals();
808 resolved = find_binpkg(virtual(name, virtuals));
809 if (resolved == NULL || !strlen(resolved))
810 resolved = find_binpkg(name);
811 } else
812 resolved = NULL;
813
814 if (resolved == NULL)
815 resolved = dep;
816
817 IF_DEBUG(fprintf(stderr, "+Atom: argv0(%s) dep(%s) resolved(%s)\n", name, dep, resolved));
818
819 if (strlen(resolved) < 1) {
820 warn("Cant find a binpkg for %s from rdepend(%s)", name, pkg->RDEPEND);
821 continue;
822 }
823
824 /* ratom = atom_explode(resolved); */
825 subpkg = grab_binpkg_info(resolved); /* free me later */
826
827 assert(subpkg != NULL);
828 IF_DEBUG(fprintf(stderr, "+Subpkg: %s/%s\n", subpkg->CATEGORY, subpkg->PF));
829
830 /* look at installed versions now. If NULL or < merge this pkg */
831 snprintf(buf, sizeof(buf), "%s/%s", subpkg->CATEGORY, subpkg->PF);
832
833 ratom = atom_explode(buf);
834
835 p = best_version(subpkg->CATEGORY, subpkg->PF);
836 /* we dont want to remerge equal versions here */
837 IF_DEBUG(fprintf(stderr, "+Installed: %s\n", p));
838 if (strlen(p) < 1)
839 if (!((strcmp(pkg->PF, subpkg->PF) == 0) && (strcmp(pkg->CATEGORY, subpkg->CATEGORY) == 0)))
840 pkg_fetch(level+1, ratom, subpkg);
841
842 atom_implode(subatom);
843 atom_implode(ratom);
844 free(subpkg);
845 } else {
846 qfprintf(stderr, "Cant explode atom %s\n", name);
847 }
848 break;
849 }
850 }
851 freeargv(ARGC, ARGV);
852 }
853 if (pretend)
854 return;
855
856 /* Set up our temp dir to unpack this stuff */
857 xasprintf(&p, "%s/qmerge/%s/%s", port_tmpdir, pkg->CATEGORY, pkg->PF);
858 mkdir_p(p, 0755);
859 xchdir(p);
860 xasprintf(&D, "%s/image", p);
861 xasprintf(&T, "%s/temp", p);
862 free(p);
863
864 /* Doesn't actually remove $PWD, just everything under it */
865 rm_rf(".");
866
867 mkdir("temp", 0755);
868 mkdir_p(portroot, 0755);
869
870 /* XXX: maybe some day we should have this step operate on the
871 * tarball directly rather than unpacking it first. */
872
873 /* split the tbz and xpak data */
874 xasprintf(&tbz2, "%s/%s/%s.tbz2", pkgdir, pkg->CATEGORY, pkg->PF);
875 assert(run_applet_l("qtbz2", "-s", tbz2, NULL) == 0);
876
877 mkdir("vdb", 0755);
878 sprintf(tbz2, "%s.xpak", pkg->PF);
879 assert(run_applet_l("qxpak", "-d", "vdb", "-x", tbz2, NULL) == 0);
880
881 free(tbz2);
882
883 /* extrct the binary package data */
884 mkdir("image", 0755);
885 snprintf(buf, sizeof(buf), BUSYBOX " tar -jx%sf %s.tar.bz2 -C image/", ((verbose > 1) ? "v" : ""), pkg->PF);
886 xsystem(buf);
887 fflush(stdout);
888
889 eat_file("vdb/DEFINED_PHASES", phases, sizeof(phases));
890 pkg_run_func("vdb", phases, "pkg_pretend", D, T);
891 pkg_run_func("vdb", phases, "pkg_setup", D, T);
892 pkg_run_func("vdb", phases, "pkg_preinst", D, T);
893
894 /* XXX: kill this off */
895 xchdir("image");
896
897 if (stat(".", &st) == -1)
898 err("Cant stat pwd");
899
900 /* Initialize INSTALL_MASK and common stuff */
901 makeargv(install_mask, &iargc, &iargv);
902 /* XXX: Would be better if INSTALL_MASK deleted from image/
903 * so we didn't have to parse it while doing merge_tree() */
904 install_mask_pwd(iargc, iargv, st);
905
906 if (strstr(features, "noinfo")) rm_rf("./usr/share/info");
907 if (strstr(features, "noman" )) rm_rf("./usr/share/man");
908 if (strstr(features, "nodoc" )) rm_rf("./usr/share/doc");
909
910 /* we dont care about the return code */
911 rmdir("./usr/share");
912
913 /* XXX: Once we kill xchdir(image), this can die too */
914 xchdir("..");
915
916 if ((contents = fopen("vdb/CONTENTS", "w")) == NULL)
917 errf("come on wtf?");
918 objs = NULL;
919 if (merge_tree("image", portroot, contents, &objs, iargc, iargv))
920 errp("failed to merge to %s", portroot);
921 fclose(contents);
922
923 freeargv(iargc, iargv);
924
925 /* run postinst */
926 pkg_run_func("vdb", phases, "pkg_postinst", D, T);
927
928 /* XXX: hmm, maybe we'll want to strip more ? */
929 unlink("vdb/environment");
930
931 /* FIXME */ /* move unmerging to around here ? */
932 /* check for an already installed pkg */
933 snprintf(buf, sizeof(buf), "%s/%s", atom->CATEGORY, pkg->PF);
934
935 /* Unmerge any stray pieces from the older version which we didn't replace */
936 p = best_version(atom->CATEGORY, atom->PN);
937 if (*p) {
938 /* XXX: Should see about merging with unmerge_packages() */
939 makeargv(p, &ARGC, &ARGV);
940 for (i = 1; i < ARGC; i++) {
941 int ret, u;
942 const char *pn;
943 char *pf;
944 char *slot = NULL;
945
946 pf = ARGV[i];
947 switch ((ret = atom_compare_str(buf, pf))) {
948 case ERROR:
949 case NOT_EQUAL:
950 continue;
951 case NEWER:
952 case OLDER:
953 case EQUAL:
954 u = 1;
955 pn = basename(pf);
956 slot = grab_vdb_item("SLOT", atom->CATEGORY, pn);
957 if (pkg->SLOT[0] && slot) {
958 if (strcmp(pkg->SLOT, slot) != 0)
959 u = 0;
960 }
961 /* We need to really set this unmerge pending after we look at contents of the new pkg */
962 if (u)
963 break;
964 continue;
965 default:
966 warn("no idea how we reached here.");
967 continue;
968 }
969
970 qprintf("%s+++%s %s %s %s\n", GREEN, NORM, buf, booga[ret], pf);
971
972 pkg_unmerge(atom->CATEGORY, pn, objs);
973 }
974 freeargv(ARGC, ARGV);
975 }
976
977 /* Clean up the package state */
978 while (objs) {
979 queue *q = objs;
980 objs = q->next;
981 free(q->name);
982 free(q->item);
983 free(q);
984 }
985 free(D);
986 free(T);
987
988 /* Update the magic counter */
989 if ((fp = fopen("vdb/COUNTER", "w")) != NULL) {
990 fputs("0", fp);
991 fclose(fp);
992 }
993
994 /* move the local vdb copy to the final place */
995 snprintf(buf, sizeof(buf), "%s%s/%s/", portroot, portvdb, pkg->CATEGORY);
996 mkdir_p(buf, 0755);
997 strcat(buf, pkg->PF);
998 if (rename("vdb", buf)) {
999 xasprintf(&p, "mv vdb '%s'", buf);
1000 xsystem(p);
1001 free(p);
1002 }
1003
1004 /* clean up our local temp dir */
1005 xchdir("..");
1006 rm_rf(pkg->PF);
1007 /* don't care about return */
1008 rmdir("../qmerge");
1009
1010 printf("%s>>>%s %s%s%s/%s%s%s\n", YELLOW, NORM, WHITE, atom->CATEGORY, NORM, CYAN, atom->PN, NORM);
1011 }
1012
1013 _q_static int
1014 pkg_unmerge(const char *cat, const char *pkgname, queue *keep)
1015 {
1016 size_t buflen;
1017 char *buf, *vdb_path, *T, phases[128];
1018 FILE *fp;
1019 int ret, fd, vdb_fd, portroot_fd;
1020 int cp_argc, cpm_argc;
1021 char **cp_argv, **cpm_argv;
1022 llist_char *dirs = NULL;
1023
1024 ret = 1;
1025 buf = NULL;
1026 vdb_path = NULL;
1027 vdb_fd = portroot_fd = fd = -1;
1028
1029 if ((strchr(pkgname, ' ') != NULL) || (strchr(cat, ' ') != NULL)) {
1030 qfprintf(stderr, "%s!!!%s '%s' '%s' (ambiguous name) specify fully-qualified pkgs\n", RED, NORM, cat, pkgname);
1031 qfprintf(stderr, "%s!!!%s %s/%s (ambiguous name) specify fully-qualified pkgs\n", RED, NORM, cat, pkgname);
1032 /* qfprintf(stderr, "%s!!!%s %s %s (ambiguous name) specify fully-qualified pkgs\n", RED, NORM, pkgname); */
1033 return 1;
1034 }
1035 printf("%s<<<%s %s%s%s/%s%s%s\n", YELLOW, NORM, WHITE, cat, NORM, CYAN, pkgname, NORM);
1036
1037 if (pretend == 100)
1038 return 0;
1039
1040 /* Get a handle to the root to play with */
1041 portroot_fd = open(portroot, O_RDONLY | O_CLOEXEC);
1042 if (portroot_fd == -1) {
1043 warnp("unable to read %s", portroot);
1044 goto done;
1045 }
1046
1047 /* Get a handle on the vdb path which we'll use everywhere else */
1048 /* Note: This vdb_path must be absolute since we use it in pkg_run_func() */
1049 xasprintf(&vdb_path, "%s%s/%s/%s/", portroot, portvdb, cat, pkgname);
1050 xasprintf(&T, "%stemp", vdb_path);
1051 vdb_fd = openat(portroot_fd, vdb_path, O_RDONLY | O_CLOEXEC);
1052 if (vdb_fd == -1) {
1053 warnp("unable to read %s", vdb_path);
1054 goto done;
1055 }
1056
1057 /* First execute the pkg_prerm step */
1058 if (!pretend) {
1059 eat_file_at(vdb_fd, "DEFINED_PHASES", phases, sizeof(phases));
1060 mkdir_p(T, 0755);
1061 pkg_run_func(vdb_path, phases, "pkg_prerm", T, T);
1062 }
1063
1064 /* Now start removing all the installed files */
1065 fd = openat(vdb_fd, "CONTENTS", O_RDONLY | O_CLOEXEC);
1066 if (fd == -1) {
1067 warnp("unable to read %s", "CONTENTS");
1068 goto done;
1069 }
1070 fp = fdopen(fd, "r");
1071 if (fp == NULL)
1072 goto done;
1073
1074 /* XXX: be nice to pull this out of the current func
1075 * so we don't keep reparsing the same env var
1076 * when unmerging multiple packages.
1077 */
1078 makeargv(config_protect, &cp_argc, &cp_argv);
1079 makeargv(config_protect_mask, &cpm_argc, &cpm_argv);
1080
1081 bool unmerge_config_protected = !!strstr(features, "config-protect-if-modified");
1082
1083 while (getline(&buf, &buflen, fp) != -1) {
1084 queue *q;
1085 contents_entry *e;
1086 char zing[20];
1087 int protected = 0;
1088 struct stat st;
1089
1090 e = contents_parse_line(buf);
1091 if (!e)
1092 continue;
1093
1094 protected = config_protected(e->name, cp_argc, cp_argv, cpm_argc, cpm_argv);
1095
1096 /* This should never happen ... */
1097 assert(e->name[0] == '/' && e->name[1] != '/');
1098
1099 /* Should we remove in order symlinks,objects,dirs ? */
1100 switch (e->type) {
1101 case CONTENTS_DIR: {
1102 /* since the dir contains files, we remove it later */
1103 llist_char *list = xmalloc(sizeof(llist_char));
1104 list->data = xstrdup(e->name);
1105 list->next = dirs;
1106 dirs = list;
1107 continue;
1108 }
1109
1110 case CONTENTS_OBJ:
1111 if (protected && unmerge_config_protected) {
1112 /* If the file wasn't modified, unmerge it */
1113 unsigned char *hash = hash_file_at(portroot_fd, e->name + 1, HASH_MD5);
1114 protected = strcmp(e->digest, (const char *)hash);
1115 }
1116 break;
1117
1118 case CONTENTS_SYM:
1119 if (fstatat(portroot_fd, e->name + 1, &st, AT_SYMLINK_NOFOLLOW)) {
1120 warnp("stat failed for %s -> '%s'", e->name, e->sym_target);
1121 continue;
1122 }
1123
1124 /* Hrm, if it isn't a symlink anymore, then leave it be */
1125 if (!S_ISLNK(st.st_mode))
1126 continue;
1127
1128 break;
1129
1130 default:
1131 warn("%s???%s %s%s%s (%d)", RED, NORM, WHITE, e->name, NORM, e->type);
1132 continue;
1133 }
1134
1135 snprintf(zing, sizeof(zing), "%s%s%s", protected ? YELLOW : GREEN, protected ? "***" : "<<<" , NORM);
1136
1137 if (protected) {
1138 qprintf("%s %s\n", zing, e->name);
1139 continue;
1140 }
1141
1142 /* See if this was updated */
1143 q = keep;
1144 while (q) {
1145 if (!strcmp(q->name, e->name)) {
1146 /* XXX: could remove this from the queue */
1147 strcpy(zing, "---");
1148 q = NULL;
1149 break;
1150 }
1151 q = q->next;
1152 }
1153
1154 /* No match, so unmerge it */
1155 if (!quiet)
1156 printf("%s %s\n", zing, e->name);
1157 if (!keep || q) {
1158 char *p;
1159
1160 if (unlinkat(portroot_fd, e->name + 1, 0))
1161 errp("could not unlink: %s%s", portroot, e->name + 1);
1162
1163 p = strrchr(e->name, '/');
1164 if (p) {
1165 *p = '\0';
1166 rmdir_r_at(portroot_fd, e->name + 1);
1167 }
1168 }
1169 }
1170
1171 fclose(fp);
1172 fd = -1;
1173
1174 /* Then remove all dirs in reverse order */
1175 while (dirs != NULL) {
1176 llist_char *list = dirs;
1177 char *dir = list->data;
1178 int rm;
1179
1180 rm = pretend ? -1 : rmdir_r_at(portroot_fd, dir + 1);
1181 qprintf("%s%s%s %s%s%s/\n", rm ? YELLOW : GREEN, rm ? "---" : "<<<",
1182 NORM, DKBLUE, dir, NORM);
1183
1184 free(list->data);
1185 free(list);
1186 dirs = dirs->next;
1187 }
1188
1189 freeargv(cp_argc, cp_argv);
1190 freeargv(cpm_argc, cpm_argv);
1191
1192 if (!pretend) {
1193 /* Then execute the pkg_postrm step */
1194 pkg_run_func(vdb_path, phases, "pkg_postrm", T, T);
1195 rm_rf(T);
1196
1197 /* Finally delete the vdb entry */
1198 rm_rf_at(portroot_fd, vdb_path);
1199
1200 /* And prune any empty vdb dirs */
1201 rmdir_r_at(portroot_fd, vdb_path);
1202 }
1203
1204 ret = 0;
1205 done:
1206 if (fd != -1)
1207 close(fd);
1208 if (vdb_fd != -1)
1209 close(vdb_fd);
1210 if (portroot_fd != -1)
1211 close(portroot_fd);
1212 free(buf);
1213 free(T);
1214 free(vdb_path);
1215
1216 return ret;
1217 }
1218
1219 _q_static int unlink_empty(const char *buf)
1220 {
1221 struct stat st;
1222 if (stat(buf, &st) != -1)
1223 if (st.st_size == 0)
1224 return unlink(buf);
1225 return -1;
1226 }
1227
1228 _q_static int match_pkg(queue *ll, const struct pkg_t *pkg)
1229 {
1230 depend_atom *atom;
1231 char buf[255], buf2[255];
1232 int match = 0;
1233
1234 snprintf(buf, sizeof(buf), "%s/%s", pkg->CATEGORY, pkg->PF);
1235 if ((atom = atom_explode(buf)) == NULL)
1236 errf("%s/%s is not a valid atom", pkg->CATEGORY, pkg->PF);
1237
1238 /* verify this is the requested package */
1239 if (strcmp(ll->name, buf) == 0)
1240 match = 1;
1241
1242 if (strcmp(ll->name, pkg->PF) == 0)
1243 match = 2;
1244
1245 snprintf(buf2, sizeof(buf2), "%s/%s", pkg->CATEGORY, atom->PN);
1246
1247 if (strcmp(ll->name, buf2) == 0)
1248 match = 3;
1249
1250 if (strcmp(ll->name, atom->PN) == 0)
1251 match = 4;
1252
1253 if (match)
1254 goto match_done;
1255
1256 if (ll->item) {
1257 depend_atom *subatom = atom_explode(ll->name);
1258 if (subatom == NULL)
1259 goto match_done;
1260 if (strcmp(atom->PN, subatom->PN) == 0)
1261 match = 1;
1262 atom_implode(subatom);
1263 }
1264
1265 match_done:
1266 atom_implode(atom);
1267
1268 return match;
1269 }
1270
1271 _q_static int
1272 pkg_verify_checksums(char *fname, const struct pkg_t *pkg, const depend_atom *atom,
1273 int strict, int display)
1274 {
1275 char *hash = NULL;
1276 int ret = 0;
1277
1278 if (nomd5)
1279 return ret;
1280
1281 if (pkg->MD5[0]) {
1282 if ((hash = (char*) hash_file(fname, HASH_MD5)) == NULL) {
1283 errf("hash is NULL for %s", fname);
1284 }
1285 if (strcmp(hash, pkg->MD5) == 0) {
1286 if (display)
1287 printf("MD5: [%sOK%s] %s %s/%s\n", GREEN, NORM, hash, atom->CATEGORY, pkg->PF);
1288 } else {
1289 if (display)
1290 warn("MD5: [%sER%s] (%s) != (%s) %s/%s", RED, NORM, hash, pkg->MD5, atom->CATEGORY, pkg->PF);
1291 ret++;
1292 }
1293 }
1294
1295 if (pkg->SHA1[0]) {
1296 hash = (char*) hash_file(fname, HASH_SHA1);
1297 if (strcmp(hash, pkg->SHA1) == 0) {
1298 if (display)
1299 qprintf("SHA1: [%sOK%s] %s %s/%s\n", GREEN, NORM, hash, atom->CATEGORY, pkg->PF);
1300 } else {
1301 if (display)
1302 warn("SHA1: [%sER%s] (%s) != (%s) %s/%s", RED, NORM, hash, pkg->SHA1, atom->CATEGORY, pkg->PF);
1303 ret++;
1304 }
1305 }
1306
1307 if (!pkg->SHA1[0] && !pkg->MD5[0])
1308 return 1;
1309
1310 if (strict && ret)
1311 errf("strict is set in features");
1312
1313 return ret;
1314 }
1315
1316 _q_static
1317 void pkg_process(queue *todo, const struct pkg_t *pkg)
1318 {
1319 queue *ll;
1320 depend_atom *atom;
1321 char buf[255];
1322
1323 snprintf(buf, sizeof(buf), "%s/%s", pkg->CATEGORY, pkg->PF);
1324 if ((atom = atom_explode(buf)) == NULL)
1325 errf("%s/%s is not a valid atom", pkg->CATEGORY, pkg->PF);
1326
1327 ll = todo;
1328 while (ll) {
1329 if (ll->name[0] != '-' && match_pkg(ll, pkg)) {
1330 /* fetch all requested packages */
1331 pkg_fetch(0, atom, pkg);
1332 }
1333
1334 ll = ll->next;
1335 }
1336
1337 /* free the atom */
1338 atom_implode(atom);
1339 }
1340
1341 _q_static void
1342 pkg_fetch(int level, const depend_atom *atom, const struct pkg_t *pkg)
1343 {
1344 char buf[_Q_PATH_MAX], str[_Q_PATH_MAX];
1345
1346 /* qmerge -pv patch */
1347 if (pretend) {
1348 if (!install) install++;
1349 /* qprint_tree_node(level, atom, pkg); */
1350 pkg_merge(level, atom, pkg);
1351 return;
1352 }
1353
1354 /* check to see if file exists and it's checksum matches */
1355 snprintf(buf, sizeof(buf), "%s/%s/%s.tbz2", pkgdir, pkg->CATEGORY, pkg->PF);
1356 unlink_empty(buf);
1357
1358 snprintf(str, sizeof(str), "%s/%s", pkgdir, pkg->CATEGORY);
1359 mkdir(str, 0755);
1360
1361 /* XXX: should do a size check here for partial downloads */
1362
1363 if (force_download && (access(buf, R_OK) == 0) && (pkg->SHA1[0] || pkg->MD5[0])) {
1364 if (pkg_verify_checksums(buf, pkg, atom, 0, 0) != 0)
1365 unlink(buf);
1366 }
1367 if (access(buf, R_OK) == 0) {
1368 if (!pkg->SHA1[0] && !pkg->MD5[0]) {
1369 warn("No checksum data for %s", buf);
1370 return;
1371 } else {
1372 if (pkg_verify_checksums(buf, pkg, atom, qmerge_strict, !quiet) == 0) {
1373 pkg_merge(0, atom, pkg);
1374 return;
1375 }
1376 }
1377 }
1378 if (verbose)
1379 printf("Fetching %s/%s.tbz2\n", atom->CATEGORY, pkg->PF);
1380
1381 /* fetch the package */
1382 /* Check CATEGORY first */
1383 if (!old_repo) {
1384 snprintf(buf, sizeof(buf), "%s/%s.tbz2", atom->CATEGORY, pkg->PF);
1385 fetch(str, buf);
1386 }
1387 snprintf(buf, sizeof(buf), "%s/%s/%s.tbz2", pkgdir, atom->CATEGORY, pkg->PF);
1388 if (access(buf, R_OK) != 0) {
1389 snprintf(buf, sizeof(buf), "%s.tbz2", pkg->PF);
1390 fetch(str, buf);
1391 old_repo = 1;
1392 }
1393
1394 /* verify the pkg exists now. unlink if zero bytes */
1395 snprintf(buf, sizeof(buf), "%s/%s/%s.tbz2", pkgdir, atom->CATEGORY, pkg->PF);
1396 unlink_empty(buf);
1397
1398 if (access(buf, R_OK) != 0) {
1399 warn("Failed to fetch %s.tbz2 from %s", pkg->PF, binhost);
1400 fflush(stderr);
1401 return;
1402 }
1403
1404 snprintf(buf, sizeof(buf), "%s/%s/%s.tbz2", pkgdir, atom->CATEGORY, pkg->PF);
1405 if (pkg_verify_checksums(buf, pkg, atom, qmerge_strict, !quiet) == 0) {
1406 pkg_merge(0, atom, pkg);
1407 return;
1408 }
1409 }
1410
1411 _q_static void
1412 print_Pkg(int full, struct pkg_t *pkg)
1413 {
1414 char *p = NULL;
1415 char buf[512];
1416 depend_atom *atom = NULL;
1417
1418 if (!pkg->CATEGORY[0]) errf("CATEGORY is NULL");
1419 if (!pkg->PF[0]) errf("PF is NULL");
1420
1421 printf("%s%s/%s%s%s%s%s%s\n", BOLD, pkg->CATEGORY, BLUE, pkg->PF, NORM,
1422 !quiet ? " [" : "",
1423 !quiet ? make_human_readable_str(pkg->SIZE, 1, KILOBYTE) : "",
1424 !quiet ? "KB]" : "");
1425
1426 if (full == 0)
1427 return;
1428
1429 if (pkg->DESC[0])
1430 printf(" %sDesc%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->DESC);
1431 if (pkg->SHA1[0])
1432 printf(" %sSha1%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->SHA1);
1433 if (pkg->MD5[0])
1434 printf(" %sMd5%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->MD5);
1435 if (!pkg->MD5[0] && !pkg->SHA1[0])
1436 printf(" %sSums%s:%s %s(MISSING!)%s\n", DKGREEN, YELLOW, NORM, RED, NORM);
1437 if (pkg->SLOT[0])
1438 printf(" %sSlot%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->SLOT);
1439 if (pkg->LICENSE[0])
1440 printf(" %sLicense%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->LICENSE);
1441 if (pkg->RDEPEND[0])
1442 printf(" %sRdepend%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->RDEPEND);
1443 if (pkg->USE[0])
1444 printf(" %sUse%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->USE);
1445 if (pkg->REPO[0])
1446 if (strcmp(pkg->REPO, "gentoo") != 0)
1447 printf(" %sRepo%s:%s %s\n", DKGREEN, YELLOW, NORM, pkg->REPO);
1448
1449 snprintf(buf, sizeof(buf), "%s/%s", pkg->CATEGORY, pkg->PF);
1450 atom = atom_explode(buf);
1451 if ((atom = atom_explode(buf)) == NULL)
1452 return;
1453 if ((p = best_version(pkg->CATEGORY, atom->PN)) != NULL) {
1454 if (*p) {
1455 int ret;
1456 const char *icolor = RED;
1457 ret = atom_compare_str(buf, p);
1458 switch (ret) {
1459 case EQUAL: icolor = RED; break;
1460 case NEWER: icolor = YELLOW; break;
1461 case OLDER: icolor = BLUE; break;
1462 default: icolor = NORM; break;
1463 }
1464 printf(" %sInstalled%s:%s %s%s%s\n", DKGREEN, YELLOW, NORM, icolor, p, NORM);
1465 }
1466 }
1467 atom_implode(atom);
1468 }
1469
1470 _q_static int
1471 unmerge_packages(queue *todo)
1472 {
1473 depend_atom *atom;
1474 char *p;
1475 int i, argc;
1476 char **argv;
1477
1478 while (todo) {
1479 char buf[512];
1480
1481 if (todo->name[0] == '-')
1482 goto next;
1483
1484 p = best_version(NULL, todo->name);
1485 if (!*p)
1486 goto next;
1487
1488 makeargv(p, &argc, &argv);
1489 for (i = 1; i < argc; ++i) {
1490 if ((atom = atom_explode(argv[i])) == NULL)
1491 continue;
1492 if (atom->CATEGORY) {
1493 atom2str(atom, buf, sizeof(buf));
1494 pkg_unmerge(atom->CATEGORY, buf, NULL);
1495 }
1496 atom_implode(atom);
1497 }
1498 freeargv(argc, argv);
1499
1500 next:
1501 todo = todo->next;
1502 }
1503
1504 return 0;
1505 }
1506
1507 _q_static FILE *
1508 open_binpkg_index(void)
1509 {
1510 FILE *fp;
1511 char buf[BUFSIZ];
1512
1513 snprintf(buf, sizeof(buf), "%s/portage/%s", port_tmpdir, Packages);
1514 fp = fopen(buf, "r");
1515 if (fp)
1516 return fp;
1517
1518 snprintf(buf, sizeof(buf), "%s/%s", pkgdir, Packages);
1519 fp = fopen(buf, "r");
1520 if (fp)
1521 return fp;
1522
1523 errp("Unable to open package file %s in %s/portage or %s",
1524 Packages, port_tmpdir, pkgdir);
1525 }
1526
1527 _q_static struct pkg_t *
1528 grab_binpkg_info(const char *name)
1529 {
1530 FILE *fp;
1531 char buf[BUFSIZ];
1532 char *p;
1533 depend_atom *atom;
1534
1535 struct pkg_t *pkg = xzalloc(sizeof(struct pkg_t));
1536 struct pkg_t *rpkg = xzalloc(sizeof(struct pkg_t));
1537
1538 static char best_match[sizeof(Pkg.PF)+2+sizeof(Pkg.CATEGORY)];
1539
1540 best_match[0] = 0;
1541
1542 fp = open_binpkg_index();
1543
1544 while (fgets(buf, sizeof(buf), fp) != NULL) {
1545 if (*buf == '\n') {
1546 if (pkg->PF[0] && pkg->CATEGORY[0]) {
1547 int ret;
1548
1549 snprintf(buf, sizeof(buf), "%s/%s", pkg->CATEGORY, pkg->PF);
1550 if (strstr(buf, name) != NULL) {
1551 if (!best_match[0])
1552 strncpy(best_match, buf, sizeof(best_match));
1553
1554 atom = atom_explode(buf);
1555 snprintf(buf, sizeof(buf), "%s/%s-%s", atom->CATEGORY, atom->PN, atom->PV);
1556 if (atom->PR_int)
1557 snprintf(buf, sizeof(buf), "%s/%s-%s-r%i", atom->CATEGORY, atom->PN, atom->PV, atom->PR_int);
1558 ret = atom_compare_str(name, buf);
1559 IF_DEBUG(fprintf(stderr, "=== atom_compare(%s, %s) = %d %s\n", name, buf, ret, booga[ret])); /* buf(%s) depend(%s)\n", ret, pkg->CATEGORY, pkg->PF, name, pkg->RDEPEND); */
1560 switch (ret) {
1561 case EQUAL:
1562 case NEWER:
1563 snprintf(buf, sizeof(buf), "%s/%s", pkg->CATEGORY, pkg->PF);
1564 ret = atom_compare_str(buf, best_match);
1565 if (ret == NEWER || ret == EQUAL) {
1566 strncpy(best_match, buf, sizeof(best_match));
1567 memcpy(rpkg, pkg, sizeof(struct pkg_t));
1568 IF_DEBUG(fprintf(stderr, "--- %s/%s depend(%s)\n", rpkg->CATEGORY, rpkg->PF, rpkg->RDEPEND));
1569 }
1570 case OLDER: break;
1571 default:
1572 break;
1573 }
1574 atom_implode(atom);
1575 }
1576 memset(pkg, 0, sizeof(struct pkg_t));
1577 }
1578 continue;
1579 }
1580 if ((p = strchr(buf, '\n')) != NULL)
1581 *p = 0;
1582
1583 if ((p = strchr(buf, ':')) == NULL)
1584 continue;
1585 if ((p = strchr(buf, ' ')) == NULL)
1586 continue;
1587 *p = 0;
1588 ++p;
1589
1590 if (*buf) {
1591 /* we dont need all the info */
1592 if (strcmp(buf, "RDEPEND:") == 0)
1593 strncpy(pkg->RDEPEND, p, sizeof(Pkg.RDEPEND));
1594 if (strcmp(buf, "PF:") == 0)
1595 strncpy(pkg->PF, p, sizeof(Pkg.PF));
1596 if (strcmp(buf, "CATEGORY:") == 0)
1597 strncpy(pkg->CATEGORY, p, sizeof(Pkg.CATEGORY));
1598 if (strcmp(buf, "REPO:") == 0)
1599 strncpy(pkg->REPO, p, sizeof(Pkg.REPO));
1600
1601 if (strcmp(buf, "CPV:") == 0) {
1602 if ((atom = atom_explode(p)) != NULL) {
1603 snprintf(buf, sizeof(buf), "%s-%s", atom->PN, atom->PV);
1604 if (atom->PR_int)
1605 snprintf(buf, sizeof(buf), "%s-%s-r%i", atom->PN, atom->PV, atom->PR_int);
1606 strncpy(pkg->PF, buf, sizeof(Pkg.PF));
1607 strncpy(pkg->CATEGORY, atom->CATEGORY, sizeof(Pkg.CATEGORY));
1608 atom_implode(atom);
1609 }
1610 }
1611 if (strcmp(buf, "SLOT:") == 0)
1612 strncpy(pkg->SLOT, p, sizeof(Pkg.SLOT));
1613 if (strcmp(buf, "USE:") == 0)
1614 strncpy(pkg->USE, p, sizeof(Pkg.USE));
1615
1616 /* checksums. We must have 1 or the other unless --*/
1617 if (strcmp(buf, "MD5:") == 0)
1618 strncpy(pkg->MD5, p, sizeof(Pkg.MD5));
1619 if (strcmp(buf, "SHA1:") == 0)
1620 strncpy(pkg->SHA1, p, sizeof(Pkg.SHA1));
1621 }
1622 }
1623 fclose(fp);
1624 free(pkg);
1625 return rpkg;
1626 }
1627
1628 _q_static char *
1629 find_binpkg(const char *name)
1630 {
1631 FILE *fp;
1632 char buf[BUFSIZ];
1633 char *p;
1634 char PF[sizeof(Pkg.PF)];
1635 char CATEGORY[sizeof(Pkg.CATEGORY)];
1636
1637 static char best_match[sizeof(Pkg.PF)+2+sizeof(Pkg.CATEGORY)];
1638
1639 best_match[0] = 0;
1640 if (NULL == name)
1641 return best_match;
1642
1643 fp = open_binpkg_index();
1644
1645 while (fgets(buf, sizeof(buf), fp) != NULL) {
1646 if (*buf == '\n') {
1647 if (PF[0] && CATEGORY[0]) {
1648 int ret;
1649 snprintf(buf, sizeof(buf), "%s/%s", CATEGORY, PF);
1650 if (strstr(buf, name) != NULL) {
1651 depend_atom *atom;
1652
1653 if (!best_match[0])
1654 strncpy(best_match, buf, sizeof(best_match));
1655
1656 atom = atom_explode(buf);
1657 snprintf(buf, sizeof(buf), "%s/%s", atom->CATEGORY, atom->PN);
1658 ret = atom_compare_str(name, buf);
1659 switch (ret) {
1660 case OLDER: break;
1661 case NEWER:
1662 case EQUAL:
1663 snprintf(buf, sizeof(buf), "%s/%s", CATEGORY, PF);
1664 ret = atom_compare_str(buf, best_match);
1665 if (ret == NEWER || ret == EQUAL)
1666 strncpy(best_match, buf, sizeof(best_match));
1667 /* printf("[%s == %s] = %d; %s/%s\n", name, buf, ret, CATEGORY, PF); */
1668 default:
1669 break;
1670 }
1671 atom_implode(atom);
1672 }
1673 }
1674 continue;
1675 }
1676 if ((p = strchr(buf, '\n')) != NULL)
1677 *p = 0;
1678
1679 if ((p = strchr(buf, ':')) == NULL)
1680 continue;
1681 if ((p = strchr(buf, ' ')) == NULL)
1682 continue;
1683 *p = 0;
1684 ++p;
1685
1686 if (*buf) {
1687 if (strcmp(buf, "CPV:") == 0) {
1688 depend_atom *atom;
1689 if ((atom = atom_explode(p)) != NULL) {
1690 snprintf(buf, sizeof(buf), "%s-%s", atom->PN, atom->PV);
1691 if (atom->PR_int)
1692 snprintf(buf, sizeof(buf), "%s-%s-r%i", atom->PN, atom->PV, atom->PR_int);
1693 strncpy(PF, buf, sizeof(PF));
1694 strncpy(CATEGORY, atom->CATEGORY, sizeof(CATEGORY));
1695 atom_implode(atom);
1696 }
1697 }
1698 if (strcmp(buf, "PF:") == 0)
1699 strncpy(PF, p, sizeof(PF));
1700 if (strcmp(buf, "CATEGORY:") == 0)
1701 strncpy(CATEGORY, p, sizeof(CATEGORY));
1702 }
1703 }
1704 fclose(fp);
1705 return best_match;
1706 }
1707
1708 _q_static int
1709 parse_packages(queue *todo)
1710 {
1711 FILE *fp;
1712 size_t buflen;
1713 char *buf, *p;
1714 long lineno = 0;
1715
1716 fp = open_binpkg_index();
1717
1718 memset(&Pkg, 0, sizeof(Pkg));
1719
1720 buf = NULL;
1721 while (getline(&buf, &buflen, fp) != -1) {
1722 lineno++;
1723 if (*buf == '\n') {
1724 if ((strlen(Pkg.PF) > 0) && (strlen(Pkg.CATEGORY) > 0)) {
1725 struct pkg_t *pkg = xmalloc(sizeof(*pkg));
1726 *pkg = Pkg;
1727
1728 if (search_pkgs) {
1729 if (todo) {
1730 queue *ll = todo;
1731 while (ll) {
1732 if ((match_pkg(ll, pkg) > 0) || (strcmp(ll->name, pkg->CATEGORY) == 0))
1733 print_Pkg(verbose, pkg);
1734 ll = ll->next;
1735 }
1736 } else
1737 print_Pkg(verbose, pkg);
1738 } else
1739 pkg_process(todo, pkg);
1740
1741 free(pkg);
1742 }
1743 memset(&Pkg, 0, sizeof(Pkg));
1744 continue;
1745 }
1746 if ((p = strchr(buf, '\n')) != NULL)
1747 *p = 0;
1748
1749 if ((p = strchr(buf, ':')) == NULL)
1750 continue;
1751 if ((p = strchr(buf, ' ')) == NULL)
1752 continue;
1753 *p = 0;
1754 ++p;
1755
1756 switch (*buf) {
1757 case 'U':
1758 if (strcmp(buf, "USE:") == 0) strncpy(Pkg.USE, p, sizeof(Pkg.USE));
1759 break;
1760 case 'P':
1761 if (strcmp(buf, "PF:") == 0) strncpy(Pkg.PF, p, sizeof(Pkg.PF));
1762 break;
1763 case 'S':
1764 if (strcmp(buf, "SIZE:") == 0) Pkg.SIZE = atol(p);
1765 if (strcmp(buf, "SLOT:") == 0) strncpy(Pkg.SLOT, p, sizeof(Pkg.SLOT));
1766 if (strcmp(buf, "SHA1:") == 0) strncpy(Pkg.SHA1, p, sizeof(Pkg.SHA1));
1767 break;
1768 case 'M':
1769 if (strcmp(buf, "MD5:") == 0) strncpy(Pkg.MD5, p, sizeof(Pkg.MD5));
1770 break;
1771 case 'R':
1772 if (strcmp(buf, "REPO:") == 0) strncpy(Pkg.REPO, p, sizeof(Pkg.REPO));
1773 if (strcmp(buf, "RDEPEND:") == 0) strncpy(Pkg.RDEPEND, p, sizeof(Pkg.RDEPEND));
1774 break;
1775 case 'L':
1776 if (strcmp(buf, "LICENSE:") == 0) strncpy(Pkg.LICENSE, p, sizeof(Pkg.LICENSE));
1777 break;
1778 case 'C':
1779 if (strcmp(buf, "CATEGORY:") == 0) strncpy(Pkg.CATEGORY, p, sizeof(Pkg.CATEGORY));
1780 if (strcmp(buf, "CPV:") == 0) {
1781 depend_atom *atom;
1782 if ((atom = atom_explode(p)) != NULL) {
1783 if (atom->PR_int)
1784 snprintf(Pkg.PF, sizeof(Pkg.PF), "%s-%s-r%i", atom->PN, atom->PV, atom->PR_int);
1785 else
1786 snprintf(Pkg.PF, sizeof(Pkg.PF), "%s-%s", atom->PN, atom->PV);
1787 strncpy(Pkg.CATEGORY, atom->CATEGORY, sizeof(Pkg.CATEGORY));
1788 atom_implode(atom);
1789 }
1790 }
1791 break;
1792 case 'D':
1793 if (strcmp(buf, "DESC:") == 0) strncpy(Pkg.DESC, p, sizeof(Pkg.DESC));
1794 break;
1795 default:
1796 break;
1797 }
1798 }
1799
1800 free(buf);
1801 fclose(fp);
1802
1803 return 0;
1804 }
1805
1806 _q_static queue *
1807 qmerge_add_set_atom(char *satom, queue *set)
1808 {
1809 char *p;
1810 const char *slot;
1811
1812 if ((p = strchr(satom, ':')) != NULL) {
1813 *p = 0;
1814 slot = p + 1;
1815 } else
1816 slot = "0";
1817
1818 return add_set(satom, slot, set);
1819 }
1820
1821 _q_static queue *
1822 qmerge_add_set_file(const char *dir, const char *file, queue *set)
1823 {
1824 FILE *fp;
1825 size_t buflen;
1826 char *buf, *fname;
1827
1828 /* Find the file to read */
1829 xasprintf(&fname, "%s%s/%s", portroot, dir, file);
1830
1831 if ((fp = fopen(fname, "r")) == NULL) {
1832 warnp("unable to read set file %s", fname);
1833 free(fname);
1834 return NULL;
1835 }
1836 free(fname);
1837
1838 /* Load each entry */
1839 buf = NULL;
1840 while (getline(&buf, &buflen, fp) != -1) {
1841 rmspace(buf);
1842 set = qmerge_add_set_atom(buf, set);
1843 }
1844 free(buf);
1845
1846 fclose(fp);
1847
1848 return set;
1849 }
1850
1851 _q_static void *
1852 qmerge_add_set_system(void *data, char *buf)
1853 {
1854 queue *set = data;
1855 char *s;
1856
1857 s = strchr(buf, '#');
1858 if (s)
1859 *s = '\0';
1860 rmspace(buf);
1861
1862 s = buf;
1863 if (*s == '*')
1864 set = add_set(s + 1, "", set);
1865 else if (s[0] == '-' && s[1] == '*') {
1866 int ok;
1867 set = del_set(s + 2, set, &ok);
1868 }
1869
1870 return set;
1871 }
1872
1873 /* XXX: note, this doesn't handle more complicated set files like
1874 * the portage .ini files in /usr/share/portage/sets/ */
1875 /* XXX: this code does not combine duplicate dependencies */
1876 _q_static queue *
1877 qmerge_add_set(char *buf, queue *set)
1878 {
1879 if (strcmp(buf, "world") == 0)
1880 return qmerge_add_set_file("/var/lib/portage", "world", set);
1881 else if (strcmp(buf, "all") == 0)
1882 return get_vdb_atoms(0);
1883 else if (strcmp(buf, "system") == 0)
1884 return q_profile_walk("packages", qmerge_add_set_system, set);
1885 else if (buf[0] == '@')
1886 return qmerge_add_set_file("/etc/portage", buf+1, set);
1887 else
1888 return qmerge_add_set_atom(buf, set);
1889 }
1890
1891 _q_static int
1892 qmerge_run(queue *todo)
1893 {
1894 if (uninstall)
1895 return unmerge_packages(todo);
1896 else
1897 return parse_packages(todo);
1898 }
1899
1900 int qmerge_main(int argc, char **argv)
1901 {
1902 int i, ret;
1903 queue *todo;
1904
1905 if (argc < 2)
1906 qmerge_usage(EXIT_FAILURE);
1907
1908 while ((i = GETOPT_LONG(QMERGE, qmerge, "")) != -1) {
1909 switch (i) {
1910 case 'f': force_download = 1; break;
1911 case 'F': force_download = 2; break;
1912 case 's': search_pkgs = 1; break;
1913 /* case 'i': case 'g': */
1914 case 'K': install = 1; break;
1915 case 'U': uninstall = 1; break;
1916 case 'p': pretend = 1; break;
1917 case 'u': update_only = 1;
1918 case 'y': interactive = 0; break;
1919 case 'O': follow_rdepends = 0; break;
1920 case '5': nomd5 = 1; break;
1921 COMMON_GETOPTS_CASES(qmerge)
1922 }
1923 }
1924
1925 qmerge_strict = (strstr("strict", features) == 0) ? 1 : 0;
1926
1927 /* Short circut this. */
1928 if (install && !pretend) {
1929 if (follow_rdepends && getenv("QMERGE") == NULL) {
1930 install = 0;
1931 warn("Using these options are likely to break your system at this point. export QMERGE=1; if you think you know what your doing.");
1932 }
1933 }
1934
1935 /* Expand any portage sets on the command line */
1936 todo = NULL;
1937 for (i = optind; i < argc; ++i)
1938 todo = qmerge_add_set(argv[i], todo);
1939
1940 if (!uninstall)
1941 qmerge_initialize();
1942
1943 /* Make sure the user wants to do it */
1944 if (interactive) {
1945 int save_pretend = pretend;
1946 int save_verbose = verbose;
1947 int save_quiet = quiet;
1948
1949 pretend = 100;
1950 verbose = 0;
1951 quiet = 1;
1952 ret = qmerge_run(todo);
1953 if (ret || save_pretend)
1954 return ret;
1955
1956 if (uninstall) {
1957 if (!prompt("OK to unmerge these packages"))
1958 return 0;
1959 } else {
1960 if (!prompt("OK to merge these packages"))
1961 return 0;
1962 }
1963
1964 pretend = save_pretend;
1965 verbose = save_verbose;
1966 quiet = save_quiet;
1967 }
1968
1969 ret = qmerge_run(todo);
1970 free_sets(todo);
1971 return ret;
1972 }
1973
1974 #else
1975 DEFINE_APPLET_STUB(qmerge)
1976 #endif

  ViewVC Help
Powered by ViewVC 1.1.20