- check signatures of content and repodata.xml files
[platform/upstream/libsolv.git] / examples / solv.c
1 /*
2  * Copyright (c) 2009, Novell Inc.
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 /* solv, a little software installer demoing the sat solver library */
9
10 /* things missing:
11  * - vendor policy loading
12  */
13
14 #define _GNU_SOURCE
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <dirent.h>
19 #include <unistd.h>
20 #include <zlib.h>
21 #include <fcntl.h>
22 #include <assert.h>
23 #include <sys/utsname.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26
27 #include "pool.h"
28 #include "poolarch.h"
29 #include "repo.h"
30 #include "util.h"
31 #include "solver.h"
32 #include "solverdebug.h"
33 #include "chksum.h"
34 #include "repo_solv.h"
35
36 #include "repo_write.h"
37 #include "repo_rpmdb.h"
38 #include "repo_products.h"
39 #include "repo_rpmmd.h"
40 #include "repo_susetags.h"
41 #include "repo_repomdxml.h"
42 #include "repo_content.h"
43 #include "pool_fileconflicts.h"
44
45
46 #ifdef FEDORA
47 # define REPOINFO_PATH "/etc/yum.repos.d"
48 #else
49 # define REPOINFO_PATH "/etc/zypp/repos.d"
50 # define PRODUCTS_PATH "/etc/products.d"
51 #endif
52
53 #define SOLVCACHE_PATH "/var/cache/solv"
54
55
56 struct repoinfo {
57   Repo *repo;
58
59   char *alias;
60   char *name;
61   int enabled;
62   int autorefresh;
63   char *baseurl;
64   char *path;
65   int type;
66   int gpgcheck;
67   int priority;
68   int keeppackages;
69 };
70
71 #define TYPE_UNKNOWN    0
72 #define TYPE_SUSETAGS   1
73 #define TYPE_RPMMD      2
74 #define TYPE_PLAINDIR   3
75
76 static int
77 read_repoinfos_sort(const void *ap, const void *bp)
78 {
79   const struct repoinfo *a = ap;
80   const struct repoinfo *b = bp;
81   return strcmp(a->alias, b->alias);
82 }
83
84 struct repoinfo *
85 read_repoinfos(Pool *pool, const char *reposdir, int *nrepoinfosp)
86 {
87   char buf[4096];
88   char buf2[4096], *kp, *vp, *kpe;
89   DIR *dir;
90   FILE *fp;
91   struct dirent *ent;
92   int l, rdlen;
93   struct repoinfo *repoinfos = 0, *cinfo;
94   int nrepoinfos = 0;
95
96   rdlen = strlen(reposdir);
97   dir = opendir(reposdir);
98   if (!dir)
99     {
100       *nrepoinfosp = 0;
101       return 0;
102     }
103   while ((ent = readdir(dir)) != 0)
104     {
105       l = strlen(ent->d_name);
106       if (l < 6 || rdlen + 2 + l >= sizeof(buf) || strcmp(ent->d_name + l - 5, ".repo") != 0)
107         continue;
108       snprintf(buf, sizeof(buf), "%s/%s", reposdir, ent->d_name);
109       if ((fp = fopen(buf, "r")) == 0)
110         {
111           perror(buf);
112           continue;
113         }
114       cinfo = 0;
115       while(fgets(buf2, sizeof(buf2), fp))
116         {
117           l = strlen(buf2);
118           if (l == 0)
119             continue;
120           while (buf2[l - 1] == '\n' || buf2[l - 1] == ' ' || buf2[l - 1] == '\t')
121             buf2[--l] = 0;
122           kp = buf2;
123           while (*kp == ' ' || *kp == '\t')
124             kp++;
125           if (!*kp || *kp == '#')
126             continue;
127           if (!cinfo)
128             {
129               if (*kp != '[')
130                 continue;
131               vp = strrchr(kp, ']');
132               if (!vp)
133                 continue;
134               *vp = 0;
135               repoinfos = sat_extend(repoinfos, nrepoinfos, 1, sizeof(*repoinfos), 15);
136               cinfo = repoinfos + nrepoinfos++;
137               memset(cinfo, 0, sizeof(*cinfo));
138               cinfo->alias = strdup(kp + 1);
139               cinfo->gpgcheck = 1;
140               cinfo->type = TYPE_RPMMD;
141               continue;
142             }
143           vp = strchr(kp, '=');
144           if (!vp)
145             continue;
146           for (kpe = vp - 1; kpe >= kp; kpe--)
147             if (*kpe != ' ' && *kpe != '\t')
148               break;
149           if (kpe == kp)
150             continue;
151           vp++;
152           while (*vp == ' ' || *vp == '\t')
153             vp++;
154           kpe[1] = 0;
155           if (!strcmp(kp, "name"))
156             cinfo->name = strdup(vp);
157           else if (!strcmp(kp, "enabled"))
158             cinfo->enabled = *vp == '0' ? 0 : 1;
159           else if (!strcmp(kp, "autorefresh"))
160             cinfo->autorefresh = *vp == '0' ? 0 : 1;
161           else if (!strcmp(kp, "gpgcheck"))
162             cinfo->gpgcheck = *vp == '0' ? 0 : 1;
163           else if (!strcmp(kp, "baseurl"))
164             cinfo->baseurl = strdup(vp);
165           else if (!strcmp(kp, "path"))
166             {
167               if (vp && strcmp(vp, "/") != 0)
168                 cinfo->path = strdup(vp);
169             }
170           else if (!strcmp(kp, "type"))
171             {
172               if (!strcmp(vp, "yast2"))
173                 cinfo->type = TYPE_SUSETAGS;
174               else if (!strcmp(vp, "rpm-md"))
175                 cinfo->type = TYPE_RPMMD;
176               else if (!strcmp(vp, "plaindir"))
177                 cinfo->type = TYPE_PLAINDIR;
178               else
179                 cinfo->type = TYPE_UNKNOWN;
180             }
181           else if (!strcmp(kp, "priority"))
182             cinfo->priority = atoi(vp);
183           else if (!strcmp(kp, "keeppackages"))
184             cinfo->keeppackages = *vp == '0' ? 0 : 1;
185         }
186       fclose(fp);
187       cinfo = 0;
188     }
189   closedir(dir);
190   qsort(repoinfos, nrepoinfos, sizeof(*repoinfos), read_repoinfos_sort);
191   *nrepoinfosp = nrepoinfos;
192   return repoinfos;
193 }
194
195 static ssize_t
196 cookie_gzread(void *cookie, char *buf, size_t nbytes)
197 {
198   return gzread((gzFile *)cookie, buf, nbytes);
199 }
200
201 static int
202 cookie_gzclose(void *cookie)
203 {
204   return gzclose((gzFile *)cookie);
205 }
206
207 FILE *
208 curlfopen(struct repoinfo *cinfo, const char *file, int uncompress, const unsigned char *chksum, Id chksumtype)
209 {
210   pid_t pid;
211   int fd, l;
212   int status;
213   char tmpl[100];
214   char url[4096];
215   const char *baseurl = cinfo->baseurl;
216
217   l = strlen(baseurl);
218   if (l && baseurl[l - 1] == '/')
219     snprintf(url, sizeof(url), "%s%s", baseurl, file);
220   else
221     snprintf(url, sizeof(url), "%s/%s", baseurl, file);
222   strcpy(tmpl, "/var/tmp/solvXXXXXX");
223   fd = mkstemp(tmpl);
224   if (fd < 0)
225     {
226       perror("mkstemp");
227       exit(1);
228     }
229   unlink(tmpl);
230   if ((pid = fork()) == (pid_t)-1)
231     {
232       perror("fork");
233       exit(1);
234     }
235   if (pid == 0)
236     {
237       if (fd != 1)
238         {
239           dup2(fd, 1);
240           close(fd);
241         }
242       execlp("curl", "curl", "-s", "-L", url, (char *)0);
243       perror("curl");
244       _exit(0);
245     }
246   while (waitpid(pid, &status, 0) != pid)
247     ;
248   if (lseek(fd, 0, SEEK_END) == 0)
249     {
250       /* empty file */
251       close(fd);
252       return 0;
253     }
254   lseek(fd, 0, SEEK_SET);
255   if (chksumtype)
256     {
257       char buf[1024];
258       unsigned char *sum;
259       void *h = sat_chksum_create(chksumtype);
260       int l;
261
262       if (!h)
263         {
264           printf("%s: unknown checksum type\n", file);
265           close(fd);
266           return 0;
267         }
268       while ((l = read(fd, buf, sizeof(buf))) > 0)
269         sat_chksum_add(h, buf, l);
270       lseek(fd, 0, SEEK_SET);
271       l = 0;
272       sum = sat_chksum_get(h, &l);
273       if (memcmp(sum, chksum, l))
274         {
275           printf("%s: checksum mismatch\n", file);
276           close(fd);
277           return 0;
278         }
279     }
280   if (uncompress)
281     {
282       cookie_io_functions_t cio;
283       gzFile *gzf;
284
285       sprintf(tmpl, "/dev/fd/%d", fd);
286       gzf = gzopen(tmpl, "r");
287       close(fd);
288       if (!gzf)
289         return 0;
290       memset(&cio, 0, sizeof(cio));
291       cio.read = cookie_gzread;
292       cio.close = cookie_gzclose;
293       return fopencookie(gzf, "r", cio);
294     }
295   fcntl(fd, F_SETFD, FD_CLOEXEC);
296   return fdopen(fd, "r");
297 }
298
299 void
300 calc_checksum_fp(FILE *fp, Id chktype, unsigned char *out)
301 {
302   char buf[4096];
303   void *h = sat_chksum_create(chktype);
304   int l;
305
306   while ((l = fread(buf, 1, sizeof(buf), fp)) > 0)
307     sat_chksum_add(h, buf, l);
308   rewind(fp);
309   sat_chksum_free(h, out);
310 }
311
312 void
313 cleanupgpg(char *gpgdir)
314 {
315   char cmd[256];
316   snprintf(cmd, sizeof(cmd), "%s/pubring.gpg", gpgdir);
317   unlink(cmd);
318   snprintf(cmd, sizeof(cmd), "%s/pubring.gpg~", gpgdir);
319   unlink(cmd);
320   snprintf(cmd, sizeof(cmd), "%s/secring.gpg", gpgdir);
321   unlink(cmd);
322   snprintf(cmd, sizeof(cmd), "%s/trustdb.gpg", gpgdir);
323   unlink(cmd);
324   snprintf(cmd, sizeof(cmd), "%s/keys", gpgdir);
325   unlink(cmd);
326   rmdir(gpgdir);
327 }
328
329 int
330 checksig(Pool *sigpool, FILE *fp, FILE *sigfp)
331 {
332   char *gpgdir;
333   char *keysfile;
334   const char *pubkey;
335   char cmd[256];
336   FILE *kfp;
337   Solvable *s;
338   Id p;
339   off_t posfp, possigfp;
340   int r;
341
342   gpgdir = mkdtemp(pool_tmpjoin(sigpool, "/var/tmp/solvgpg.XXXXXX", 0, 0));
343   if (!gpgdir)
344     return 0;
345   keysfile = pool_tmpjoin(sigpool, gpgdir, "/keys", 0);
346   if (!(kfp = fopen(keysfile, "w")) )
347     {
348       cleanupgpg(gpgdir);
349       return 0;
350     }
351   for (p = 1, s = sigpool->solvables + p; p < sigpool->nsolvables; p++, s++)
352     {
353       if (!s->repo)
354         continue;
355       pubkey = solvable_lookup_str(s, SOLVABLE_DESCRIPTION);
356       if (!pubkey || !*pubkey)
357         continue;
358       if (fwrite(pubkey, strlen(pubkey), 1, kfp) != 1)
359         break;
360       if (fputc('\n', kfp) == EOF)      /* Just in case... */
361         break;
362     }
363   if (fclose(kfp))
364     {
365       cleanupgpg(gpgdir);
366       return 0;
367     }
368   snprintf(cmd, sizeof(cmd), "gpg -q --homedir %s --import %s", gpgdir, keysfile);
369   if (system(cmd))
370     {
371       fprintf(stderr, "key import error\n");
372       cleanupgpg(gpgdir);
373       return 0;
374     }
375   unlink(keysfile);
376   posfp = lseek(fileno(fp), 0, SEEK_CUR);
377   lseek(fileno(fp), 0, SEEK_SET);
378   possigfp = lseek(fileno(sigfp), 0, SEEK_CUR);
379   lseek(fileno(sigfp), 0, SEEK_SET);
380   snprintf(cmd, sizeof(cmd), "gpg -q --homedir %s --verify /dev/fd/%d /dev/fd/%d >/dev/null 2>&1", gpgdir, fileno(sigfp), fileno(fp));
381   fcntl(fileno(fp), F_SETFD, 0);        /* clear CLOEXEC */
382   fcntl(fileno(sigfp), F_SETFD, 0);     /* clear CLOEXEC */
383   r = system(cmd);
384   lseek(fileno(sigfp), possigfp, SEEK_SET);
385   lseek(fileno(fp), posfp, SEEK_SET);
386   fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
387   fcntl(fileno(sigfp), F_SETFD, FD_CLOEXEC);
388   cleanupgpg(gpgdir);
389   return r == 0 ? 1 : 0;
390 }
391
392 void
393 calc_checksum_stat(struct stat *stb, Id chktype, unsigned char *out)
394 {
395   void *h = sat_chksum_create(chktype);
396   sat_chksum_add(h, &stb->st_dev, sizeof(stb->st_dev));
397   sat_chksum_add(h, &stb->st_ino, sizeof(stb->st_ino));
398   sat_chksum_add(h, &stb->st_size, sizeof(stb->st_size));
399   sat_chksum_add(h, &stb->st_mtime, sizeof(stb->st_mtime));
400   sat_chksum_free(h, out);
401 }
402
403 void
404 setarch(Pool *pool)
405 {
406   struct utsname un;
407   if (uname(&un))
408     {
409       perror("uname");
410       exit(1);
411     }
412   pool_setarch(pool, un.machine);
413 }
414
415 char *calccachepath(Repo *repo)
416 {
417   char *q, *p = pool_tmpjoin(repo->pool, SOLVCACHE_PATH, "/", repo->name);
418   p = pool_tmpjoin(repo->pool, p, ".solv", 0);
419   q = p + strlen(SOLVCACHE_PATH) + 1;
420   if (*q == '.')
421     *q = '_';
422   for (; *q; q++)
423     if (*q == '/')
424       *q = '_';
425   return p;
426 }
427
428 int
429 usecachedrepo(Repo *repo, unsigned char *cookie)
430 {
431   FILE *fp;
432   unsigned char mycookie[32];
433
434   if (!(fp = fopen(calccachepath(repo), "r")))
435     return 0;
436   if (fseek(fp, -sizeof(mycookie), SEEK_END) || fread(mycookie, sizeof(mycookie), 1, fp) != 1)
437     {
438       fclose(fp);
439       return 0;
440     }
441   if (cookie && memcmp(cookie, mycookie, sizeof(mycookie)))
442     {
443       fclose(fp);
444       return 0;
445     }
446   rewind(fp);
447   if (repo_add_solv(repo, fp))
448     {
449       fclose(fp);
450       return 0;
451     }
452   fclose(fp);
453   return 1;
454 }
455
456 void
457 writecachedrepo(Repo *repo, unsigned char *cookie)
458 {
459   Id *addedfileprovides = 0;
460   FILE *fp;
461   int i, fd;
462   char *tmpl;
463   Repodata *info;
464
465   mkdir(SOLVCACHE_PATH, 0755);
466   tmpl = sat_dupjoin(SOLVCACHE_PATH, "/", ".newsolv-XXXXXX");
467   fd = mkstemp(tmpl);
468   if (!fd)
469     {
470       free(tmpl);
471       return;
472     }
473   fchmod(fd, 0444);
474   if (!(fp = fdopen(fd, "w")))
475     {
476       close(fd);
477       unlink(tmpl);
478       free(tmpl);
479       return;
480     }
481   info = repo_add_repodata(repo, 0);
482   pool_addfileprovides_ids(repo->pool, 0, &addedfileprovides);
483   if (addedfileprovides && *addedfileprovides)
484     {
485       for (i = 0; addedfileprovides[i]; i++)
486         repodata_add_idarray(info, SOLVID_META, REPOSITORY_ADDEDFILEPROVIDES, addedfileprovides[i]);
487     }
488   sat_free(addedfileprovides);
489   repodata_internalize(info);
490   repo_write(repo, fp, 0, 0, 0);
491   repodata_free(info);
492   if (fwrite(cookie, 32, 1, fp) != 1)
493     {
494       fclose(fp);
495       unlink(tmpl);
496       free(tmpl);
497       return;
498     }
499   if (fclose(fp))
500     {
501       unlink(tmpl);
502       free(tmpl);
503       return;
504     }
505   if (!rename(tmpl, calccachepath(repo)))
506     unlink(tmpl);
507   free(tmpl);
508 }
509
510 static Pool *
511 read_sigs()
512 {
513   Pool *sigpool = pool_create();
514   Repo *repo = repo_create(sigpool, "rpmdbkeys");
515   repo_add_rpmdb_pubkeys(repo, 0, 0);
516   return sigpool;
517 }
518
519 void
520 read_repos(Pool *pool, struct repoinfo *repoinfos, int nrepoinfos)
521 {
522   Repo *repo;
523   struct repoinfo *cinfo;
524   int i;
525   FILE *fp;
526   Dataiterator di;
527   const char *primaryfile;
528   const unsigned char *primaryfilechksum;
529   Id primaryfilechksumtype;
530   const char *descrdir;
531   int defvendor;
532   struct stat stb;
533   unsigned char cookie[32];
534   Pool *sigpool = 0;
535
536   repo = repo_create(pool, "@System");
537   printf("rpm database:");
538   if (stat("/var/lib/rpm/Packages", &stb))
539     memset(&stb, 0, sizeof(&stb));
540   calc_checksum_stat(&stb, REPOKEY_TYPE_SHA256, cookie);
541   if (usecachedrepo(repo, cookie))
542     printf(" cached\n");
543   else
544     {
545       FILE *ofp;
546       printf(" reading\n");
547       int done = 0;
548
549 #ifdef PRODUCTS_PATH
550       repo_add_products(repo, PRODUCTS_PATH, 0, REPO_NO_INTERNALIZE);
551 #endif
552       if ((ofp = fopen(calccachepath(repo), "r")) != 0)
553         {
554           Repo *ref = repo_create(pool, "@System.old");
555           if (!repo_add_solv(ref, ofp))
556             {
557               repo_add_rpmdb(repo, ref, 0, REPO_REUSE_REPODATA);
558               done = 1;
559             }
560           fclose(ofp);
561           repo_free(ref, 1);
562         }
563       if (!done)
564         repo_add_rpmdb(repo, 0, 0, REPO_REUSE_REPODATA);
565       writecachedrepo(repo, cookie);
566     }
567   pool_set_installed(pool, repo);
568
569   for (i = 0; i < nrepoinfos; i++)
570     {
571       cinfo = repoinfos + i;
572       if (!cinfo->enabled)
573         continue;
574
575       repo = repo_create(pool, cinfo->alias);
576       cinfo->repo = repo;
577       repo->appdata = cinfo;
578       repo->priority = 99 - cinfo->priority;
579
580       if (!cinfo->autorefresh && usecachedrepo(repo, 0))
581         {
582           printf("repo '%s':", cinfo->alias);
583           printf(" cached\n");
584           continue;
585         }
586       switch (cinfo->type)
587         {
588         case TYPE_RPMMD:
589           printf("rpmmd repo '%s':", cinfo->alias);
590           fflush(stdout);
591           if ((fp = curlfopen(cinfo, "repodata/repomd.xml", 0, 0, 0)) == 0)
592             {
593               printf(" no repomd.xml file, skipped\n");
594               repo_free(repo, 1);
595               cinfo->repo = 0;
596               break;
597             }
598           calc_checksum_fp(fp, REPOKEY_TYPE_SHA256, cookie);
599           if (usecachedrepo(repo, cookie))
600             {
601               printf(" cached\n");
602               fclose(fp);
603               break;
604             }
605           if (cinfo->gpgcheck)
606             {
607               FILE *sigfp;
608               if ((sigfp = curlfopen(cinfo, "repodata/repomd.xml.asc", 0, 0, 0)) == 0)
609                 {
610                   printf(" unsigned, skipped\n");
611                   fclose(fp);
612                   break;
613                 }
614               if (!sigpool)
615                 sigpool = read_sigs();
616               if (!checksig(sigpool, fp, sigfp))
617                 {
618                   printf(" checksig failed, skipped\n");
619                   fclose(sigfp);
620                   fclose(fp);
621                   break;
622                 }
623               fclose(sigfp);
624             }
625           repo_add_repomdxml(repo, fp, 0);
626           fclose(fp);
627           primaryfile = 0;
628           primaryfilechksum = 0;
629           primaryfilechksumtype = 0;
630           dataiterator_init(&di, pool, repo, SOLVID_META, REPOSITORY_REPOMD_TYPE, "primary", SEARCH_STRING);
631           dataiterator_prepend_keyname(&di, REPOSITORY_REPOMD);
632           if (dataiterator_step(&di))
633             {
634               dataiterator_setpos_parent(&di);
635               primaryfile = pool_lookup_str(pool, SOLVID_POS, REPOSITORY_REPOMD_LOCATION);
636               primaryfilechksum = pool_lookup_bin_checksum(pool, SOLVID_POS, REPOSITORY_REPOMD_CHECKSUM, &primaryfilechksumtype);
637             }
638           dataiterator_free(&di);
639           if (!primaryfile)
640             {
641               printf(" no primary file entry, skipped\n");
642               break;
643             }
644           if (!primaryfilechksumtype)
645             {
646               printf(" no primary file checksum, skipped\n");
647               break;
648             }
649           printf(" reading\n");
650           if ((fp = curlfopen(cinfo, primaryfile, 1, primaryfilechksum, primaryfilechksumtype)) == 0)
651             continue;
652           repo_add_rpmmd(repo, fp, 0, 0);
653           fclose(fp);
654           writecachedrepo(repo, cookie);
655           break;
656         case TYPE_SUSETAGS:
657           printf("susetags repo '%s':", cinfo->alias);
658           fflush(stdout);
659           repo = repo_create(pool, cinfo->alias);
660           cinfo->repo = repo;
661           repo->appdata = cinfo;
662           repo->priority = 99 - cinfo->priority;
663           descrdir = 0;
664           defvendor = 0;
665           if ((fp = curlfopen(cinfo, "content", 0, 0, 0)) == 0)
666             {
667               printf(" no content file, skipped\n");
668               repo_free(repo, 1);
669               cinfo->repo = 0;
670               break;
671             }
672           calc_checksum_fp(fp, REPOKEY_TYPE_SHA256, cookie);
673           if (usecachedrepo(repo, cookie))
674             {
675               printf(" cached\n");
676               fclose(fp);
677               break;
678             }
679           if (cinfo->gpgcheck)
680             {
681               FILE *sigfp;
682               if ((sigfp = curlfopen(cinfo, "content.asc", 0, 0, 0)) == 0)
683                 {
684                   printf(" unsigned, skipped\n");
685                   fclose(fp);
686                   break;
687                 }
688               if (!sigpool)
689                 sigpool = read_sigs();
690               if (!checksig(sigpool, fp, sigfp))
691                 {
692                   printf(" checksig failed, skipped\n");
693                   fclose(sigfp);
694                   fclose(fp);
695                   break;
696                 }
697             }
698           repo_add_content(repo, fp, 0);
699           fclose(fp);
700           defvendor = repo_lookup_id(repo, SOLVID_META, SUSETAGS_DEFAULTVENDOR);
701           descrdir = repo_lookup_str(repo, SOLVID_META, SUSETAGS_DESCRDIR);
702           if (!descrdir)
703             descrdir = "suse/setup/descr";
704           primaryfile = 0;
705           dataiterator_init(&di, pool, repo, SOLVID_META, SUSETAGS_FILE_NAME, "packages.gz", SEARCH_STRING);
706           dataiterator_prepend_keyname(&di, SUSETAGS_FILE);
707           if (dataiterator_step(&di))
708             {
709               dataiterator_setpos_parent(&di);
710               primaryfilechksum = pool_lookup_bin_checksum(pool, SOLVID_POS, SUSETAGS_FILE_CHECKSUM, &primaryfilechksumtype);
711               primaryfile = "packages.gz";
712             }
713           dataiterator_free(&di);
714           if (!primaryfile)
715             {
716               dataiterator_init(&di, pool, repo, SOLVID_META, SUSETAGS_FILE_NAME, "packages", SEARCH_STRING);
717               dataiterator_prepend_keyname(&di, SUSETAGS_FILE);
718               if (dataiterator_step(&di))
719                 {
720                   dataiterator_setpos_parent(&di);
721                   primaryfilechksum = pool_lookup_bin_checksum(pool, SOLVID_POS, SUSETAGS_FILE_CHECKSUM, &primaryfilechksumtype);
722                   primaryfile = "packages";
723                 }
724               dataiterator_free(&di);
725             }
726           if (!primaryfile)
727             {
728               printf(" no packages file entry, skipped\n");
729               break;
730             }
731           if (!primaryfilechksumtype)
732             {
733               printf(" no packages file checksum, skipped\n");
734               break;
735             }
736           printf(" reading\n");
737           if ((fp = curlfopen(cinfo, pool_tmpjoin(pool, descrdir, "/", primaryfile), !strcmp(primaryfile, "packages.gz") ? 1 : 0, primaryfilechksum, primaryfilechksumtype)) == 0)
738             break;
739           repo_add_susetags(repo, fp, defvendor, 0, 0);
740           fclose(fp);
741           writecachedrepo(repo, cookie);
742           break;
743         default:
744           printf("unsupported repo '%s': skipped\n", cinfo->alias);
745           repo_free(repo, 1);
746           cinfo->repo = 0;
747           break;
748         }
749     }
750   if (sigpool)
751     pool_free(sigpool);
752 }
753
754 void
755 mkselect(Pool *pool, const char *arg, int flags, Queue *out)
756 {
757   Id id, p, pp;
758   Id type = 0;
759   const char *r, *r2;
760
761   id = str2id(pool, arg, 0);
762   if (id)
763     {
764       FOR_PROVIDES(p, pp, id)
765         {
766           Solvable *s = pool_id2solvable(pool, p);
767           if (s->name == id)
768             {
769               type = SOLVER_SOLVABLE_NAME;
770               break;
771             }
772           type = SOLVER_SOLVABLE_PROVIDES;
773         }
774     }
775   if (!type)
776     {
777       /* did not find a solvable, see if it's a relation */
778       if ((r = strpbrk(arg, "<=>")) != 0)
779         {
780           Id rid, rname, revr;
781           int rflags = 0;
782           for (r2 = r; r2 > arg && (r2[-1] == ' ' || r2[-1] == '\t'); )
783             r2--;
784           rname = r2 > arg ? strn2id(pool, arg, r2 - arg, 1) : 0;
785           for (; *r; r++)
786             {
787               if (*r == '<')
788                 rflags |= REL_LT;
789               else if (*r == '=')
790                 rflags |= REL_EQ;
791               else if (*r == '>')
792                 rflags |= REL_GT;
793               else
794                 break;
795             }
796           while (*r == ' ' || *r == '\t')
797             r++;
798           revr = *r ? str2id(pool, r, 1) : 0;
799           rid = rname && revr ? rel2id(pool, rname, revr, rflags, 1) : 0;
800           if (rid)
801             {
802               FOR_PROVIDES(p, pp, rid)
803                 {
804                   Solvable *s = pool_id2solvable(pool, p);
805                   if (pool_match_nevr(pool, s, rid))
806                     {
807                       type = SOLVER_SOLVABLE_NAME;
808                       break;
809                     }
810                   type = SOLVER_SOLVABLE_PROVIDES;
811                 }
812             }
813           if (type)
814             id = rid;
815         }
816     }
817   if (type)
818     {
819       queue_push(out, type);
820       queue_push(out, id);
821     }
822 }
823
824 int
825 yesno(const char *str)
826 {
827   char inbuf[128], *ip;
828
829   for (;;)
830     {
831       printf("%s", str);
832       fflush(stdout);
833       *inbuf = 0;
834       if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
835         {
836           printf("Abort.\n");
837           exit(1);
838         }
839       while (*ip == ' ' || *ip == '\t')
840         ip++;
841       if (*ip == 'q')
842         {
843           printf("Abort.\n");
844           exit(1);
845         }
846       if (*ip == 'y' || *ip == 'n')
847         return *ip == 'y' ? 1 : 0;
848     }
849 }
850
851 struct fcstate {
852   FILE **newpkgsfps;
853   Queue *checkq;
854   int newpkgscnt;
855   void *rpmdbstate;
856 };
857
858 static void *
859 fc_cb(Pool *pool, Id p, void *cbdata)
860 {
861   struct fcstate *fcstate = cbdata;
862   Solvable *s;
863   Id rpmdbid;
864   int i;
865   FILE *fp;
866
867   if (!p)
868     {
869       rpm_byrpmdbid(0, 0, &fcstate->rpmdbstate);
870       return 0;
871     }
872   s = pool_id2solvable(pool, p);
873   if (pool->installed && s->repo == pool->installed)
874     {
875       if (!s->repo->rpmdbid)
876         return 0;
877       rpmdbid = s->repo->rpmdbid[p - s->repo->start];
878       if (!rpmdbid)
879         return 0;
880        return rpm_byrpmdbid(rpmdbid, 0, &fcstate->rpmdbstate);
881     }
882   for (i = 0; i < fcstate->newpkgscnt; i++)
883     if (fcstate->checkq->elements[i] == p)
884       break;
885   if (i == fcstate->newpkgscnt)
886     return 0;
887   fp = fcstate->newpkgsfps[i];
888   if (!fp)
889     return 0;
890   rewind(fp);
891   return rpm_byfp(fp, solvable2str(pool, s), &fcstate->rpmdbstate);
892 }
893
894 void
895 runrpm(const char *arg, const char *name, int dupfd3)
896 {
897   pid_t pid;
898   int status;
899
900   if ((pid = fork()) == (pid_t)-1)
901     {
902       perror("fork");
903       exit(1);
904     }
905   if (pid == 0)
906     {
907       if (dupfd3 != -1 && dupfd3 != 3)
908         {
909           dup2(dupfd3, 3);
910           close(dupfd3);
911         }
912       if (dupfd3 != -1)
913         fcntl(3, F_SETFD, 0);   /* clear CLOEXEC */
914       if (strcmp(arg, "-e") == 0)
915         execlp("rpm", "rpm", arg, "--nodeps", "--nodigest", "--nosignature", name, (char *)0);
916       else
917         execlp("rpm", "rpm", arg, "--force", "--nodeps", "--nodigest", "--nosignature", name, (char *)0);
918       perror("rpm");
919       _exit(0);
920     }
921   while (waitpid(pid, &status, 0) != pid)
922     ;
923   if (status)
924     {
925       printf("rpm failed\n");
926       exit(1);
927     }
928 }
929
930 static Id
931 nscallback(Pool *pool, void *data, Id name, Id evr)
932 {
933   if (name == NAMESPACE_PRODUCTBUDDY)
934     {    
935       /* SUSE specific hack: each product has an associated rpm */
936       Solvable *s = pool->solvables + evr; 
937       Id p, pp, cap; 
938       
939       cap = str2id(pool, pool_tmpjoin(pool, "product(", id2str(pool, s->name) + 8, ")"), 0);
940       if (!cap)
941         return 0;
942       cap = rel2id(pool, cap, s->evr, REL_EQ, 0);
943       if (!cap)
944         return 0;
945       FOR_PROVIDES(p, pp, cap) 
946         {
947           Solvable *ps = pool->solvables + p; 
948           if (ps->repo == s->repo && ps->arch == s->arch)
949             break;
950         }
951       return p;
952     }
953   return 0;
954 }
955
956
957 int
958 main(int argc, char **argv)
959 {
960   Pool *pool;
961   Id p, pp;
962   struct repoinfo *repoinfos;
963   int nrepoinfos = 0;
964   int i, mode, newpkgs;
965   Queue job, checkq;
966   Solver *solv = 0;
967   Transaction *trans;
968   char inbuf[128], *ip;
969   int updateall = 0;
970   int distupgrade = 0;
971   FILE **newpkgsfps;
972   struct fcstate fcstate;
973
974   argc--;
975   argv++;
976   if (!argv[0])
977     {
978       fprintf(stderr, "Usage: solv install|erase|update|show <select>\n");
979       exit(1);
980     }
981   if (!strcmp(argv[0], "install") || !strcmp(argv[0], "in"))
982     mode = SOLVER_INSTALL;
983   else if (!strcmp(argv[0], "erase") || !strcmp(argv[0], "rm"))
984     mode = SOLVER_ERASE;
985   else if (!strcmp(argv[0], "show"))
986     mode = 0;
987   else if (!strcmp(argv[0], "update") || !strcmp(argv[0], "up"))
988     mode = SOLVER_UPDATE;
989   else if (!strcmp(argv[0], "dist-upgrade") || !strcmp(argv[0], "dup"))
990     {
991       mode = SOLVER_UPDATE;
992       distupgrade = 1;
993     }
994   else
995     {
996       fprintf(stderr, "Usage: solv install|erase|update|show <select>\n");
997       exit(1);
998     }
999
1000   pool = pool_create();
1001   pool->nscallback = nscallback;
1002   // pool_setdebuglevel(pool, 2);
1003   setarch(pool);
1004   repoinfos = read_repoinfos(pool, REPOINFO_PATH, &nrepoinfos);
1005   read_repos(pool, repoinfos, nrepoinfos);
1006   // FOR_REPOS(i, repo)
1007   //   printf("%s: %d solvables\n", repo->name, repo->nsolvables);
1008   pool_addfileprovides(pool);
1009   pool_createwhatprovides(pool);
1010
1011   queue_init(&job);
1012   for (i = 1; i < argc; i++)
1013     mkselect(pool, argv[i], 0, &job);
1014   if (!job.count && mode == SOLVER_UPDATE)
1015     updateall = 1;
1016   else if (!job.count)
1017     {
1018       printf("no package matched\n");
1019       exit(1);
1020     }
1021
1022   if (!mode)
1023     {
1024       /* show mode, no solver needed */
1025       for (i = 0; i < job.count; i += 2)
1026         {
1027           FOR_JOB_SELECT(p, pp, job.elements[i], job.elements[i + 1])
1028             {
1029               Solvable *s = pool_id2solvable(pool, p);
1030               printf("  - %s [%s]\n", solvable2str(pool, s), s->repo->name);
1031             }
1032         }
1033       exit(0);
1034     }
1035
1036   // add mode
1037   for (i = 0; i < job.count; i += 2)
1038     job.elements[i] |= mode;
1039
1040   // multiversion test
1041   // queue_push2(&job, SOLVER_NOOBSOLETES|SOLVER_SOLVABLE_NAME, str2id(pool, "kernel-pae", 1));
1042   // queue_push2(&job, SOLVER_NOOBSOLETES|SOLVER_SOLVABLE_NAME, str2id(pool, "kernel-pae-base", 1));
1043   // queue_push2(&job, SOLVER_NOOBSOLETES|SOLVER_SOLVABLE_NAME, str2id(pool, "kernel-pae-extra", 1));
1044
1045 rerunsolver:
1046   for (;;)
1047     {
1048       Id problem, solution;
1049       int pcnt, scnt;
1050
1051       solv = solver_create(pool);
1052       solv->ignorealreadyrecommended = 1;
1053       solv->updatesystem = updateall;
1054       solv->dosplitprovides = updateall;
1055       if (updateall && distupgrade)
1056         {
1057           solv->distupgrade = 1;
1058           solv->allowdowngrade = 1;
1059           solv->allowarchchange = 1;
1060           solv->allowvendorchange = 1;
1061         }
1062       // queue_push2(&job, SOLVER_DISTUPGRADE, 3);
1063       solver_solve(solv, &job);
1064       if (!solv->problems.count)
1065         break;
1066       pcnt = solver_problem_count(solv);
1067       printf("Found %d problems:\n", pcnt);
1068       for (problem = 1; problem <= pcnt; problem++)
1069         {
1070           int take = 0;
1071           printf("Problem %d:\n", problem);
1072           solver_printprobleminfo(solv, problem);
1073           printf("\n");
1074           scnt = solver_solution_count(solv, problem);
1075           for (solution = 1; solution <= scnt; solution++)
1076             {
1077               printf("Solution %d:\n", solution);
1078               solver_printsolution(solv, problem, solution);
1079               printf("\n");
1080             }
1081           for (;;)
1082             {
1083               printf("Please choose a solution: ");
1084               fflush(stdout);
1085               *inbuf = 0;
1086               if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
1087                 {
1088                   printf("Abort.\n");
1089                   exit(1);
1090                 }
1091               while (*ip == ' ' || *ip == '\t')
1092                 ip++;
1093               if (*ip >= '0' && *ip <= '9')
1094                 {
1095                   take = atoi(ip);
1096                   if (take >= 1 && take <= scnt)
1097                     break;
1098                 }
1099               if (*ip == 's')
1100                 {
1101                   take = 0;
1102                   break;
1103                 }
1104               if (*ip == 'q')
1105                 {
1106                   printf("Abort.\n");
1107                   exit(1);
1108                 }
1109             }
1110           if (!take)
1111             continue;
1112           solver_take_solution(solv, problem, take, &job);
1113         }
1114       solver_free(solv);
1115       solv = 0;
1116     }
1117   if (!solv->trans.steps.count)
1118     {
1119       printf("Nothing to do.\n");
1120       exit(1);
1121     }
1122   printf("\n");
1123   printf("Transaction summary:\n\n");
1124   solver_printtransaction(solv);
1125   if (!yesno("OK to continue (y/n)? "))
1126     {
1127       printf("Abort.\n");
1128       exit(1);
1129     }
1130
1131   trans = &solv->trans;
1132   queue_init(&checkq);
1133   newpkgs = transaction_installedresult(trans, &checkq);
1134   newpkgsfps = 0;
1135
1136   if (newpkgs)
1137     {
1138       printf("Downloading %d packages\n", newpkgs);
1139       newpkgsfps = sat_calloc(newpkgs, sizeof(*newpkgsfps));
1140       for (i = 0; i < newpkgs; i++)
1141         {
1142           unsigned int medianr;
1143           char *loc;
1144           Solvable *s;
1145           struct repoinfo *cinfo;
1146           const unsigned char *chksum;
1147           Id chksumtype;
1148
1149           p = checkq.elements[i];
1150           s = pool_id2solvable(pool, p);
1151           cinfo = s->repo->appdata;
1152           if (!cinfo)
1153             {
1154               printf("%s: no repository information\n", s->repo->name);
1155               exit(1);
1156             }
1157           loc = solvable_get_location(s, &medianr);
1158           if (!loc)
1159              continue;
1160           if (cinfo->type == TYPE_SUSETAGS)
1161             {
1162               const char *datadir = repo_lookup_str(cinfo->repo, SOLVID_META, SUSETAGS_DATADIR);
1163               loc = pool_tmpjoin(pool, datadir ? datadir : "suse", "/", loc);
1164             }
1165           chksumtype = 0;
1166           chksum = solvable_lookup_bin_checksum(s, SOLVABLE_CHECKSUM, &chksumtype);
1167           if ((newpkgsfps[i] = curlfopen(cinfo, loc, 0, chksum, chksumtype)) == 0)
1168             {
1169               printf("%s: %s not found in repository\n", s->repo->name, loc);
1170               exit(1);
1171             }
1172         }
1173     }
1174
1175   if (newpkgs)
1176     {
1177       Queue conflicts;
1178
1179       printf("Searching for file conflicts\n");
1180       queue_init(&conflicts);
1181       fcstate.rpmdbstate = 0;
1182       fcstate.newpkgscnt = newpkgs;
1183       fcstate.checkq = &checkq;
1184       fcstate.newpkgsfps = newpkgsfps;
1185       pool_findfileconflicts(pool, &checkq, newpkgs, &conflicts, &fc_cb, &fcstate);
1186       if (conflicts.count)
1187         {
1188           printf("\n");
1189           for (i = 0; i < conflicts.count; i += 5)
1190             printf("file %s of package %s conflicts with package %s\n", id2str(pool, conflicts.elements[i]), solvid2str(pool, conflicts.elements[i + 1]), solvid2str(pool, conflicts.elements[i + 3]));
1191           printf("\n");
1192           if (yesno("Re-run solver (y/n/q)? "))
1193             {
1194               for (i = 0; i < newpkgs; i++)
1195                 if (newpkgsfps[i])
1196                   fclose(newpkgsfps[i]);
1197               newpkgsfps = sat_free(newpkgsfps);
1198               solver_free(solv);
1199               pool_add_fileconflicts_deps(pool, &conflicts);
1200               pool_createwhatprovides(pool);    /* Hmm... */
1201               goto rerunsolver;
1202             }
1203         }
1204       queue_free(&conflicts);
1205     }
1206
1207   printf("Committing transaction:\n\n");
1208   transaction_order(trans, 0);
1209   for (i = 0; i < trans->steps.count; i++)
1210     {
1211       const char *evr, *evrp, *nvra;
1212       Solvable *s;
1213       int j;
1214       FILE *fp;
1215
1216       p = trans->steps.elements[i];
1217       s = pool_id2solvable(pool, p);
1218       Id type = transaction_type(trans, p, SOLVER_TRANSACTION_RPM_ONLY);
1219       switch(type)
1220         {
1221         case SOLVER_TRANSACTION_ERASE:
1222           printf("erase %s\n", solvid2str(pool, p));
1223           if (!s->repo->rpmdbid || !s->repo->rpmdbid[p - s->repo->start])
1224             continue;
1225           /* strip epoch from evr */
1226           evr = evrp = id2str(pool, s->evr);
1227           while (*evrp >= '0' && *evrp <= '9')
1228             evrp++;
1229           if (evrp > evr && evrp[0] == ':' && evrp[1])
1230             evr = evrp + 1;
1231           nvra = pool_tmpjoin(pool, id2str(pool, s->name), "-", evr);
1232           nvra = pool_tmpjoin(pool, nvra, ".", id2str(pool, s->arch));
1233           runrpm("-e", nvra, -1);       /* to bad that --querybynumber doesn't work */
1234           break;
1235         case SOLVER_TRANSACTION_INSTALL:
1236         case SOLVER_TRANSACTION_MULTIINSTALL:
1237           printf("install %s\n", solvid2str(pool, p));
1238           for (j = 0; j < newpkgs; j++)
1239             if (checkq.elements[j] == p)
1240               break;
1241           fp = j < newpkgs ? newpkgsfps[j] : 0;
1242           if (!fp)
1243             continue;
1244           rewind(fp);
1245           lseek(fileno(fp), 0, SEEK_SET);
1246           runrpm(type == SOLVER_TRANSACTION_MULTIINSTALL ? "-i" : "-U", "/dev/fd/3", fileno(fp));
1247           fclose(fp);
1248           newpkgsfps[j] = 0;
1249           break;
1250         default:
1251           break;
1252         }
1253     }
1254
1255   for (i = 0; i < newpkgs; i++)
1256     if (newpkgsfps[i])
1257       fclose(newpkgsfps[i]);
1258   sat_free(newpkgsfps);
1259   queue_free(&checkq);
1260   solver_free(solv);
1261   queue_free(&job);
1262   pool_free(pool);
1263   exit(0);
1264 }