- close fds before calling rpm
[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, int dupfd3, FILE **newpkgsfps, int newpkgs)
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       int i;
457       for (i = 0; i < newpkgs; i++)
458         {
459           if (!newpkgsfps[i])
460             continue;
461           if (dupfd3 != -1 && fileno(newpkgsfps[i]) == dupfd3)
462             continue;
463           close(fileno(newpkgsfps[i]));
464         }
465       if (dupfd3 != -1 && dupfd3 != 3)
466         {
467           dup2(dupfd3, 3);
468           close(dupfd3);
469         }
470       if (strcmp(arg, "-e") == 0)
471         execlp("rpm", "rpm", arg, "--nodeps", "--nodigest", "--nosignature", name, (char *)0);
472       else
473         execlp("rpm", "rpm", arg, "--force", "--nodeps", "--nodigest", "--nosignature", name, (char *)0);
474       perror("rpm");
475       _exit(0);
476     }
477   while (waitpid(pid, &status, 0) != pid)
478     ;
479   if (status)
480     {
481       printf("rpm failed\n");
482       exit(1);
483     }
484 }
485
486 int
487 main(int argc, char **argv)
488 {
489   Pool *pool;
490   Id p, pp;
491   struct repoinfo *repoinfos;
492   int nrepoinfos = 0;
493   int i, mode, newpkgs;
494   Queue job, checkq;
495   Solver *solv = 0;
496   Transaction *trans;
497   char inbuf[128], *ip;
498   int updateall = 0;
499   FILE **newpkgsfps = 0;
500   Id *newpkgsps = 0;
501   struct fcstate fcstate;
502
503   if (!strcmp(argv[1], "install") || !strcmp(argv[1], "in"))
504     mode = SOLVER_INSTALL;
505   else if (!strcmp(argv[1], "erase") || !strcmp(argv[1], "rm"))
506     mode = SOLVER_ERASE;
507   else if (!strcmp(argv[1], "show"))
508     mode = 0;
509   else if (!strcmp(argv[1], "update") || !strcmp(argv[1], "up"))
510     mode = SOLVER_UPDATE;
511   else
512     {
513       fprintf(stderr, "Usage: solv install|erase|update|show <select>\n");
514       exit(1);
515     }
516
517   pool = pool_create();
518   // pool_setdebuglevel(pool, 2);
519   setarch(pool);
520   repoinfos = read_repoinfos(pool, "/etc/zypp/repos.d", &nrepoinfos);
521   read_repos(pool, repoinfos, nrepoinfos);
522   // FOR_REPOS(i, repo)
523   //   printf("%s: %d solvables\n", repo->name, repo->nsolvables);
524   pool_addfileprovides(pool);
525   pool_createwhatprovides(pool);
526
527   queue_init(&job);
528   for (i = 2; i < argc; i++)
529     mkselect(pool, argv[i], 0, &job);
530   if (!job.count && mode == SOLVER_UPDATE)
531     updateall = 1;
532   else if (!job.count)
533     {
534       printf("nothing matched\n");
535       exit(1);
536     }
537   if (!mode)
538     {
539       for (i = 0; i < job.count; i += 2)
540         {
541           FOR_JOB_SELECT(p, pp, job.elements[i], job.elements[i + 1])
542             {
543               Solvable *s = pool_id2solvable(pool, p);
544               printf("  - %s [%s]\n", solvable2str(pool, s), s->repo->name);
545             }
546         }
547       exit(0);
548     }
549   // add mode
550   for (i = 0; i < job.count; i += 2)
551     job.elements[i] |= mode;
552
553 rerunsolver:
554   for (;;)
555     {
556       Id problem, solution;
557       int pcnt, scnt;
558
559       solv = solver_create(pool);
560       solv->ignorealreadyrecommended = 1;
561       solv->updatesystem = updateall;
562       solver_solve(solv, &job);
563       if (!solv->problems.count)
564         break;
565       pcnt = solver_problem_count(solv);
566       printf("Found %d problems:\n", pcnt);
567       for (problem = 1; problem <= pcnt; problem++)
568         {
569           int take = 0;
570           printf("Problem %d:\n", problem);
571           solver_printprobleminfo(solv, problem);
572           printf("\n");
573           scnt = solver_solution_count(solv, problem);
574           for (solution = 1; solution <= pcnt; solution++)
575             {
576               printf("Solution %d:\n", solution);
577               solver_printsolution(solv, problem, solution);
578               printf("\n");
579             }
580           for (;;)
581             {
582               printf("Please choose a solution: ");
583               fflush(stdout);
584               *inbuf = 0;
585               if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
586                 {
587                   printf("Abort.\n");
588                   exit(1);
589                 }
590               while (*ip == ' ' || *ip == '\t')
591                 ip++;
592               if (*ip >= '0' && *ip <= '9')
593                 {
594                   take = atoi(ip);
595                   if (take >= 1 && take <= solution)
596                     break;
597                 }
598               if (*ip == 's')
599                 {
600                   take = 0;
601                   break;
602                 }
603               if (*ip == 'q')
604                 {
605                   printf("Abort.\n");
606                   exit(1);
607                 }
608             }
609           if (!take)
610             continue;
611           solver_take_solution(solv, problem, take, &job);
612         }
613       solver_free(solv);
614       solv = 0;
615     }
616   if (!solv->trans.steps.count)
617     {
618       printf("Nothing to do.\n");
619       exit(1);
620     }
621   printf("\n");
622   printf("Transaction summary:\n\n");
623   solver_printtransaction(solv);
624   if (!yesno("OK to continue (y/n)? "))
625     {
626       printf("Abort.\n");
627       exit(1);
628     }
629
630   trans = &solv->trans;
631   queue_init(&checkq);
632   newpkgs = transaction_installedresult(trans, &checkq);
633   if (newpkgs)
634     {
635       Queue conflicts;
636       printf("Downloading %d packages\n", newpkgs);
637       newpkgsfps = sat_calloc(newpkgs, sizeof(*newpkgsfps));
638       newpkgsps = sat_calloc(newpkgs, sizeof(Id));
639       for (i = 0; i < newpkgs; i++)
640         {
641           int j;
642           unsigned int medianr;
643           char *loc;
644           Solvable *s;
645           struct repoinfo *cinfo;
646
647           p = checkq.elements[i];
648           newpkgsps[i] = p;
649           s = pool_id2solvable(pool, p);
650           loc = solvable_get_location(s, &medianr);
651           cinfo = 0;
652           for (j = 0; j < nrepoinfos; j++)
653             if (s->repo == repoinfos[j].repo)
654               {
655                 cinfo = repoinfos + j;
656                 break;
657               }
658           if ((newpkgsfps[i] = curlfopen(cinfo->baseurl, loc, 0)) == 0)
659             {
660               printf("%s: %s not found\n", cinfo->alias, loc);
661               exit(1);
662             }
663         }
664       printf("Searching for file conflicts\n");
665       queue_init(&conflicts);
666       fcstate.rpmdbstate = 0;
667       fcstate.newpkgscnt = newpkgs;
668       fcstate.newpkgsfps = newpkgsfps;
669       fcstate.newpkgsps = newpkgsps;
670       pool_findfileconflicts(pool, &checkq, newpkgs, &conflicts, &fc_cb, &fcstate);
671       if (conflicts.count)
672         {
673           for (i = 0; i < newpkgs; i++)
674             fclose(newpkgsfps[i]);
675           sat_free(newpkgsfps);
676           sat_free(newpkgsps);
677           solver_free(solv);
678           printf("\n");
679           for (i = 0; i < conflicts.count; i += 5)
680             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]));
681           printf("\n");
682           if (!yesno("Re-run solver (y/n)? "))
683             {
684               printf("Abort.\n");
685               exit(1);
686             }
687           pool_add_fileconflicts_deps(pool, &conflicts);
688           pool_createwhatprovides(pool);
689           goto rerunsolver;
690         }
691       queue_free(&conflicts);
692     }
693   transaction_order(trans, 0);
694
695   printf("Committing transaction:\n\n");
696   for (i = 0; i < trans->steps.count; i++)
697     {
698       char rpmname[256];
699       const char *evr, *evrp;
700       Solvable *s;
701       int j;
702       FILE *fp;
703
704       p = trans->steps.elements[i];
705       s = pool_id2solvable(pool, p);
706       Id type = transaction_type(trans, p, SOLVER_TRANSACTION_RPM_ONLY);
707       switch(type)
708         {
709         case SOLVER_TRANSACTION_ERASE:
710           printf("erase %s\n", solvid2str(pool, p));
711           if (strlen(solvid2str(pool, p)) > 255)
712             continue;
713           evr = evrp = id2str(pool, s->evr);
714           while (*evrp >= '0' && *evrp <= '9')
715             evrp++;
716           if (evrp > evr && evrp[0] == ':' && evrp[1])
717             evr = evrp + 1;
718           sprintf(rpmname, "%s-%s.%s", id2str(pool, s->name), evr, id2str(pool, s->arch));
719           runrpm("-e", rpmname, -1, newpkgsfps, newpkgs);
720           break;
721         case SOLVER_TRANSACTION_INSTALL:
722         case SOLVER_TRANSACTION_MULTIINSTALL:
723           printf("install %s\n", solvid2str(pool, p));
724           for (j = 0; j < newpkgs; j++)
725             if (newpkgsps[j] == p)
726               break;
727           fp = j < newpkgs ? newpkgsfps[j] : 0;
728           rewind(fp);
729           lseek(fileno(fp), 0, SEEK_SET);
730           runrpm(type == SOLVER_TRANSACTION_MULTIINSTALL ? "-i" : "-U", "/dev/fd/3", fileno(fp), newpkgsfps, newpkgs);
731           fclose(fp);
732           newpkgsfps[j] = 0;
733           break;
734         default:
735           break;
736         }
737     }
738   for (i = 0; i < newpkgs; i++)
739     if (newpkgsfps[i])
740       fclose(newpkgsfps[i]);
741   sat_free(newpkgsfps);
742   sat_free(newpkgsps);
743   solver_free(solv);
744   pool_free(pool);
745   exit(0);
746 }