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