Merge branch 'master' of git@git.opensuse.org:projects/zypp/sat-solver
[platform/upstream/libsolv.git] / examples / solv.c
1 #define _GNU_SOURCE
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <dirent.h>
6 #include <unistd.h>
7 #include <zlib.h>
8 #include <sys/utsname.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11
12 #include "pool.h"
13 #include "poolarch.h"
14 #include "repo.h"
15 #include "util.h"
16 #include "solver.h"
17 #include "solverdebug.h"
18
19 #include "repo_rpmdb.h"
20 #include "repo_rpmmd.h"
21 #include "repo_susetags.h"
22 #include "repo_repomdxml.h"
23 #include "pool_fileconflicts.h"
24
25 /* solv, a little demo application */
26
27 struct repoinfo {
28   Repo *repo;
29
30   char *alias;
31   char *name;
32   int enabled;
33   int autorefresh;
34   char *baseurl;
35   char *path;
36   int type;
37   int priority;
38   int keeppackages;
39 };
40
41 #define TYPE_UNKNOWN 0
42 #define TYPE_SUSETAGS 1
43 #define TYPE_RPMMD 2
44
45 struct repoinfo *
46 read_repoinfos(Pool *pool, const char *reposdir, int *nrepoinfosp)
47 {
48   char buf[4096];
49   char buf2[4096], *kp, *vp, *kpe;
50   DIR *dir;
51   FILE *fp;
52   struct dirent *ent;
53   int l, rdlen;
54   struct repoinfo *repoinfos = 0, *cinfo;
55   int nrepoinfos = 0;
56
57   rdlen = strlen(reposdir);
58   dir = opendir(reposdir);
59   if (!dir)
60     {
61       *nrepoinfosp = 0;
62       return 0;
63     }
64   while ((ent = readdir(dir)) != 0)
65     {
66       l = strlen(ent->d_name);
67       if (l < 6 || rdlen + 2 + l >= sizeof(buf) || strcmp(ent->d_name + l - 5, ".repo") != 0)
68         continue;
69       snprintf(buf, sizeof(buf), "%s/%s", reposdir, ent->d_name);
70       if ((fp = fopen(buf, "r")) == 0)
71         {
72           perror(buf);
73           continue;
74         }
75       cinfo = 0;
76       while(fgets(buf2, sizeof(buf2), fp))
77         {
78           l = strlen(buf2);
79           if (l == 0)
80             continue;
81           while (buf2[l - 1] == '\n' || buf2[l - 1] == ' ' || buf2[l - 1] == '\t')
82             buf2[--l] = 0;
83           kp = buf2;
84           while (*kp == ' ' || *kp == '\t')
85             kp++;
86           if (!*kp || *kp == '#')
87             continue;
88           if (!cinfo)
89             {
90               if (*kp != '[')
91                 continue;
92               vp = strrchr(kp, ']');
93               if (!vp)
94                 continue;
95               *vp = 0;
96               repoinfos = sat_extend(repoinfos, nrepoinfos, 1, sizeof(*repoinfos), 15);
97               cinfo = repoinfos + nrepoinfos++;
98               memset(cinfo, 0, sizeof(*cinfo));
99               cinfo->alias = strdup(kp + 1);
100               continue;
101             }
102           vp = strchr(kp, '=');
103           if (!vp)
104             continue;
105           for (kpe = vp - 1; kpe >= kp; kpe--)
106             if (*kpe != ' ' && *kpe != '\t')
107               break;
108           if (kpe == kp)
109             continue;
110           vp++;
111           while (*vp == ' ' || *vp == '\t')
112             vp++;
113           kpe[1] = 0;
114           if (!strcmp(kp, "name"))
115             cinfo->name = strdup(vp);
116           else if (!strcmp(kp, "enabled"))
117             cinfo->enabled = *vp == '0' ? 0 : 1;
118           else if (!strcmp(kp, "autorefresh"))
119             cinfo->autorefresh = *vp == '0' ? 0 : 1;
120           else if (!strcmp(kp, "baseurl"))
121             cinfo->baseurl = strdup(vp);
122           else if (!strcmp(kp, "path"))
123             cinfo->path = strdup(vp);
124           else if (!strcmp(kp, "type"))
125             {
126               if (!strcmp(vp, "yast2"))
127                 cinfo->type = TYPE_SUSETAGS;
128               else if (!strcmp(vp, "rpm-md"))
129                 cinfo->type = TYPE_RPMMD;
130               else
131                 cinfo->type = TYPE_UNKNOWN;
132             }
133           else if (!strcmp(kp, "priority"))
134             cinfo->priority = atoi(vp);
135           else if (!strcmp(kp, "keeppackages"))
136             cinfo->keeppackages = *vp == '0' ? 0 : 1;
137         }
138       fclose(fp);
139       if (cinfo->type == TYPE_SUSETAGS && cinfo->baseurl)
140         {
141           char *old = cinfo->baseurl;
142           cinfo->baseurl = sat_malloc(5 + strlen(old) + 1);
143           sprintf(cinfo->baseurl, "%s/suse", old);
144           free(old);
145         }
146       cinfo = 0;
147     }
148   closedir(dir);
149   *nrepoinfosp = nrepoinfos;
150   return repoinfos;
151 }
152
153 static ssize_t
154 cookie_gzread(void *cookie, char *buf, size_t nbytes)
155 {
156   return gzread((gzFile *)cookie, buf, nbytes);
157 }
158
159 static int
160 cookie_gzclose(void *cookie)
161 {
162   return gzclose((gzFile *)cookie);
163 }
164
165 FILE *
166 myfopen(const char *fn)
167 {
168   cookie_io_functions_t cio;
169   char *suf;
170   gzFile *gzf;
171
172   if (!fn)
173     return 0;
174   suf = strrchr(fn, '.');
175   if (!suf || strcmp(suf, ".gz") != 0)
176     return fopen(fn, "r");
177   gzf = gzopen(fn, "r");
178   if (!gzf)
179     return 0;
180   memset(&cio, 0, sizeof(cio));
181   cio.read = cookie_gzread;
182   cio.close = cookie_gzclose;
183   return  fopencookie(gzf, "r", cio);
184 }
185
186 FILE *
187 curlfopen(char *baseurl, char *file, int uncompress)
188 {
189   pid_t pid;
190   int fd, l;
191   int status;
192   char tmpl[100];
193   char url[4096];
194
195   l = strlen(baseurl);
196   if (l && baseurl[l - 1] == '/')
197     snprintf(url, sizeof(url), "%s%s", baseurl, file);
198   else
199     snprintf(url, sizeof(url), "%s/%s", baseurl, file);
200   strcpy(tmpl, "/var/tmp/solvXXXXXX");
201   fd = mkstemp(tmpl);
202   if (fd < 0)
203     {
204       perror("mkstemp");
205       exit(1);
206     }
207   unlink(tmpl);
208   if ((pid = fork()) == (pid_t)-1)
209     {
210       perror("fork");
211       exit(1);
212     }
213   if (pid == 0)
214     {
215       if (fd != 1)
216         {
217           dup2(fd, 1);
218           close(fd);
219         }
220       execlp("curl", "curl", "-s", "-L", url, (char *)0);
221       perror("curl");
222       _exit(0);
223     }
224   while (waitpid(pid, &status, 0) != pid)
225     ;
226   lseek(fd, 0, SEEK_SET);
227   if (uncompress)
228     {
229       cookie_io_functions_t cio;
230       gzFile *gzf;
231
232       sprintf(tmpl, "/dev/fd/%d", fd);
233       gzf = gzopen(tmpl, "r");
234       close(fd);
235       if (!gzf)
236         return 0;
237       memset(&cio, 0, sizeof(cio));
238       cio.read = cookie_gzread;
239       cio.close = cookie_gzclose;
240       return fopencookie(gzf, "r", cio);
241     }
242   return fdopen(fd, "r");
243 }
244
245 void
246 setarch(Pool *pool)
247 {
248   struct utsname un;
249   if (uname(&un))
250     {
251       perror("uname");
252       exit(1);
253     }
254   pool_setarch(pool, un.machine);
255 }
256
257 void
258 read_repos(Pool *pool, struct repoinfo *repoinfos, int nrepoinfos)
259 {
260   Repo *repo;
261   struct repoinfo *cinfo;
262   int i;
263   FILE *fp;
264
265   printf("reading rpm database\n");
266   repo = repo_create(pool, "@System");
267   repo_add_rpmdb(repo, 0, 0, 0);
268   pool_set_installed(pool, repo);
269   for (i = 0; i < nrepoinfos; i++)
270     {
271       cinfo = repoinfos + i;
272       if (!cinfo->enabled)
273         continue;
274       switch (cinfo->type)
275         {
276         case TYPE_RPMMD:
277           printf("reading rpmmd repo '%s'\n", cinfo->alias);
278           if ((fp = curlfopen(cinfo->baseurl, "repodata/repomd.xml", 0)) == 0)
279             break;
280           repo = repo_create(pool, cinfo->alias);
281           cinfo->repo = repo;
282           repo_add_repomdxml(repo, fp, 0);
283           fclose(fp);
284           if ((fp = curlfopen(cinfo->baseurl, "repodata/primary.xml.gz", 1)) == 0)
285             continue;
286           repo_add_rpmmd(repo, fp, 0, 0);
287           fclose(fp);
288           break;
289         case TYPE_SUSETAGS:
290           printf("reading susetags repo '%s'\n", cinfo->alias);
291           if ((fp = curlfopen(cinfo->baseurl, "setup/descr/packages.gz", 1)) == 0)
292             break;
293           repo = repo_create(pool, cinfo->alias);
294           cinfo->repo = repo;
295           repo_add_susetags(repo, fp, 0, 0, 0);
296           fclose(fp);
297           break;
298         default:
299           printf("skipping unknown repo '%s'\n", cinfo->alias);
300           break;
301         }
302     }
303 }
304
305 void
306 mkselect(Pool *pool, const char *arg, int flags, Queue *out)
307 {
308   Id id, p, pp;
309   Id type = 0;
310   const char *r, *r2;
311
312   id = str2id(pool, arg, 0);
313   if (id)
314     {
315       FOR_PROVIDES(p, pp, id)
316         {
317           Solvable *s = pool_id2solvable(pool, p);
318           if (s->name == id)
319             {
320               type = SOLVER_SOLVABLE_NAME;
321               break;
322             }
323           type = SOLVER_SOLVABLE_PROVIDES;
324         }
325     }
326   if (!type)
327     {
328       /* did not find a solvable, see if it's a relation */
329       if ((r = strpbrk(arg, "<=>")) != 0)
330         {
331           Id rid, rname, revr;
332           int rflags = 0;
333           for (r2 = r; r2 > arg && (r2[-1] == ' ' || r2[-1] == '\t'); )
334             r2--;
335           rname = r2 > arg ? strn2id(pool, arg, r2 - arg, 1) : 0;
336           for (; *r; r++)
337             {
338               if (*r == '<')
339                 rflags |= REL_LT;
340               else if (*r == '=')
341                 rflags |= REL_EQ;
342               else if (*r == '>')
343                 rflags |= REL_GT;
344               else
345                 break;
346             }
347           while (*r == ' ' || *r == '\t')
348             r++;
349           revr = *r ? str2id(pool, r, 1) : 0;
350           rid = rname && revr ? rel2id(pool, rname, revr, rflags, 1) : 0;
351           if (rid)
352             {
353               FOR_PROVIDES(p, pp, rid)
354                 {
355                   Solvable *s = pool_id2solvable(pool, p);
356                   if (pool_match_nevr(pool, s, rid))
357                     {
358                       type = SOLVER_SOLVABLE_NAME;
359                       break;
360                     }
361                   type = SOLVER_SOLVABLE_PROVIDES;
362                 }
363             }
364           if (type)
365             id = rid;
366         }
367     }
368   if (type)
369     {
370       queue_push(out, type);
371       queue_push(out, id);
372     }
373 }
374
375 int
376 yesno(const char *str)
377 {
378   char inbuf[128], *ip;
379
380   for (;;)
381     {
382       printf("%s", str);
383       fflush(stdout);
384       *inbuf = 0;
385       if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
386         {
387           printf("Abort.\n");
388           exit(1);
389         }
390       while (*ip == ' ' || *ip == '\t')
391         ip++;
392       if (*ip == 'q')
393         {
394           printf("Abort.\n");
395           exit(1);
396         }
397       if (*ip == 'y' || *ip == 'n')
398         return *ip == 'y' ? 1 : 0;
399     }
400 }
401
402 struct fcstate {
403   FILE **newpkgsfps;
404   Id *newpkgsps;
405   int newpkgscnt;
406   void *rpmdbstate;
407 };
408
409 static void *
410 fc_cb(Pool *pool, Id p, void *cbdata)
411 {
412   struct fcstate *fcstate = cbdata;
413   Solvable *s;
414   Id rpmdbid;
415   int i;
416   FILE *fp;
417
418   if (!p)
419     {
420       rpm_byrpmdbid(0, 0, &fcstate->rpmdbstate);
421       return 0;
422     }
423   s = pool_id2solvable(pool, p);
424   if (pool->installed && s->repo == pool->installed)
425     {
426       if (!s->repo->rpmdbid)
427         return 0;
428       rpmdbid = s->repo->rpmdbid[p - s->repo->start];
429       if (!rpmdbid)
430         return 0;
431        return rpm_byrpmdbid(rpmdbid, 0, &fcstate->rpmdbstate);
432     }
433   for (i = 0; i < fcstate->newpkgscnt; i++)
434     if (fcstate->newpkgsps[i] == p)
435       break;
436   if (i == fcstate->newpkgscnt)
437     return 0;
438   fp = fcstate->newpkgsfps[i];
439   rewind(fp);
440   return rpm_byfp(fp, solvable2str(pool, s), &fcstate->rpmdbstate);
441 }
442
443 void
444 runrpm(const char *arg, const char *name)
445 {
446   pid_t pid;
447   int status;
448
449   if ((pid = fork()) == (pid_t)-1)
450     {
451       perror("fork");
452       exit(1);
453     }
454   if (pid == 0)
455     {
456       if (strcmp(arg, "-e") == 0)
457         execlp("rpm", "rpm", arg, "--nodeps", "--nodigest", "--nosignature", name, (char *)0);
458       else
459         execlp("rpm", "rpm", arg, "--force", "--nodeps", "--nodigest", "--nosignature", name, (char *)0);
460       perror("rpm");
461       _exit(0);
462     }
463   while (waitpid(pid, &status, 0) != pid)
464     ;
465   if (status)
466     {
467       printf("rpm failed\n");
468       exit(1);
469     }
470 }
471
472 int
473 main(int argc, char **argv)
474 {
475   Pool *pool;
476   Id p, pp;
477   struct repoinfo *repoinfos;
478   int nrepoinfos = 0;
479   int i, mode, newpkgs;
480   Queue job, checkq;
481   Solver *solv = 0;
482   Transaction *trans;
483   char inbuf[128], *ip;
484   int updateall = 0;
485   FILE **newpkgsfps = 0;
486   Id *newpkgsps = 0;
487   struct fcstate fcstate;
488
489   pool = pool_create();
490   // pool_setdebuglevel(pool, 2);
491   setarch(pool);
492   repoinfos = read_repoinfos(pool, "/etc/zypp/repos.d", &nrepoinfos);
493   read_repos(pool, repoinfos, nrepoinfos);
494   // FOR_REPOS(i, repo)
495   //   printf("%s: %d solvables\n", repo->name, repo->nsolvables);
496   pool_addfileprovides(pool);
497   pool_createwhatprovides(pool);
498   if (!strcmp(argv[1], "install") || !strcmp(argv[1], "in"))
499     mode = SOLVER_INSTALL;
500   else if (!strcmp(argv[1], "erase") || !strcmp(argv[1], "rm"))
501     mode = SOLVER_ERASE;
502   else if (!strcmp(argv[1], "show"))
503     mode = 0;
504   else if (!strcmp(argv[1], "update") || !strcmp(argv[1], "up"))
505     mode = SOLVER_UPDATE;
506   else
507     {
508       fprintf(stderr, "Usage: solv install|erase|update|show <select>\n");
509       exit(1);
510     }
511   queue_init(&job);
512   for (i = 2; i < argc; i++)
513     mkselect(pool, argv[i], 0, &job);
514   if (!job.count && mode == SOLVER_UPDATE)
515     updateall = 1;
516   else if (!job.count)
517     {
518       printf("nothing matched\n");
519       exit(1);
520     }
521   if (!mode)
522     {
523       for (i = 0; i < job.count; i += 2)
524         {
525           FOR_JOB_SELECT(p, pp, job.elements[i], job.elements[i + 1])
526             printf("  - %s\n", solvid2str(pool, p));
527         }
528       exit(0);
529     }
530   // add mode
531   for (i = 0; i < job.count; i += 2)
532     job.elements[i] |= mode;
533
534 rerunsolver:
535   for (;;)
536     {
537       Id problem, solution;
538       int pcnt, scnt;
539
540       solv = solver_create(pool);
541       solv->ignorealreadyrecommended = 1;
542       solv->updatesystem = updateall;
543       solver_solve(solv, &job);
544       if (!solv->problems.count)
545         break;
546       pcnt = solver_problem_count(solv);
547       printf("Found %d problems:\n", pcnt);
548       for (problem = 1; problem <= pcnt; problem++)
549         {
550           int take = 0;
551           printf("Problem %d:\n", problem);
552           solver_printprobleminfo(solv, problem);
553           printf("\n");
554           scnt = solver_solution_count(solv, problem);
555           for (solution = 1; solution <= pcnt; solution++)
556             {
557               printf("Solution %d:\n", solution);
558               solver_printsolution(solv, problem, solution);
559               printf("\n");
560             }
561           for (;;)
562             {
563               printf("Please choose a solution: ");
564               fflush(stdout);
565               *inbuf = 0;
566               if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
567                 {
568                   printf("Abort.\n");
569                   exit(1);
570                 }
571               while (*ip == ' ' || *ip == '\t')
572                 ip++;
573               if (*ip >= '0' && *ip <= '9')
574                 {
575                   take = atoi(ip);
576                   if (take >= 1 && take <= solution)
577                     break;
578                 }
579               if (*ip == 's')
580                 {
581                   take = 0;
582                   break;
583                 }
584               if (*ip == 'q')
585                 {
586                   printf("Abort.\n");
587                   exit(1);
588                 }
589             }
590           if (!take)
591             continue;
592           solver_take_solution(solv, problem, take, &job);
593         }
594       solver_free(solv);
595       solv = 0;
596     }
597   if (!solv->trans.steps.count)
598     {
599       printf("Nothing to do.\n");
600       exit(1);
601     }
602   printf("\n");
603   printf("Transaction summary:\n\n");
604   solver_printtransaction(solv);
605   if (!yesno("OK to continue (y/n)? "))
606     {
607       printf("Abort.\n");
608       exit(1);
609     }
610
611   trans = &solv->trans;
612   queue_init(&checkq);
613   newpkgs = transaction_installedresult(trans, &checkq);
614   if (newpkgs)
615     {
616       Queue conflicts;
617       printf("Downloading %d packages\n", newpkgs);
618       newpkgsfps = sat_calloc(newpkgs, sizeof(*newpkgsfps));
619       newpkgsps = sat_calloc(newpkgs, sizeof(Id));
620       for (i = 0; i < newpkgs; i++)
621         {
622           int j;
623           unsigned int medianr;
624           char *loc;
625           Solvable *s;
626           struct repoinfo *cinfo;
627
628           p = checkq.elements[i];
629           newpkgsps[i] = p;
630           s = pool_id2solvable(pool, p);
631           loc = solvable_get_location(s, &medianr);
632           cinfo = 0;
633           for (j = 0; j < nrepoinfos; j++)
634             if (s->repo == repoinfos[j].repo)
635               {
636                 cinfo = repoinfos + j;
637                 break;
638               }
639           if ((newpkgsfps[i] = curlfopen(cinfo->baseurl, loc, 0)) == 0)
640             {
641               printf("%s: %s not found\n", cinfo->alias, loc);
642               exit(1);
643             }
644         }
645       printf("Searching for file conflicts\n");
646       queue_init(&conflicts);
647       fcstate.rpmdbstate = 0;
648       fcstate.newpkgscnt = newpkgs;
649       fcstate.newpkgsfps = newpkgsfps;
650       fcstate.newpkgsps = newpkgsps;
651       pool_findfileconflicts(pool, &checkq, newpkgs, &conflicts, &fc_cb, &fcstate);
652       if (conflicts.count)
653         {
654           for (i = 0; i < newpkgs; i++)
655             fclose(newpkgsfps[i]);
656           sat_free(newpkgsfps);
657           sat_free(newpkgsps);
658           solver_free(solv);
659           printf("\n");
660           for (i = 0; i < conflicts.count; i += 5)
661             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]));
662           printf("\n");
663           if (!yesno("Re-run solver (y/n)? "))
664             {
665               printf("Abort.\n");
666               exit(1);
667             }
668           pool_add_fileconflicts_deps(pool, &conflicts);
669           pool_createwhatprovides(pool);
670           goto rerunsolver;
671         }
672       queue_free(&conflicts);
673     }
674   transaction_order(trans, 0);
675
676   printf("Committing transaction:\n");
677   for (i = 0; i < trans->steps.count; i++)
678     {
679       char rpmname[256];
680       const char *evr, *evrp;
681       Solvable *s;
682       int j;
683       FILE *fp;
684
685       p = trans->steps.elements[i];
686       s = pool_id2solvable(pool, p);
687       Id type = transaction_type(trans, p, SOLVER_TRANSACTION_RPM_ONLY);
688       switch(type)
689         {
690         case SOLVER_TRANSACTION_ERASE:
691           printf("erase %s\n", solvid2str(pool, p));
692           if (strlen(solvid2str(pool, p)) > 255)
693             continue;
694           evr = evrp = id2str(pool, s->evr);
695           while (*evrp >= '0' && *evrp <= '9')
696             evrp++;
697           if (evrp > evr && evrp[0] == ':' && evrp[1])
698             evr = evrp + 1;
699           sprintf(rpmname, "%s-%s.%s", id2str(pool, s->name), evr, id2str(pool, s->arch));
700           runrpm("-e", rpmname);
701           break;
702         case SOLVER_TRANSACTION_INSTALL:
703         case SOLVER_TRANSACTION_MULTIINSTALL:
704           printf("install %s\n", solvid2str(pool, p));
705           for (j = 0; j < newpkgs; j++)
706             if (newpkgsps[j] == p)
707               break;
708           fp = j < newpkgs ? newpkgsfps[j] : 0;
709           rewind(fp);
710           lseek(fileno(fp), 0, SEEK_SET);
711           sprintf(rpmname, "/dev/fd/%d", fileno(fp));
712           runrpm(type == SOLVER_TRANSACTION_MULTIINSTALL ? "-i" : "-U", rpmname);
713           break;
714         default:
715           break;
716         }
717     }
718   for (i = 0; i < newpkgs; i++)
719     fclose(newpkgsfps[i]);
720   sat_free(newpkgsfps);
721   sat_free(newpkgsps);
722   solver_free(solv);
723   pool_free(pool);
724   exit(0);
725 }