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