Merge pull request #1 from jeremyroman/master
[mspang/inapt.git] / inapt.cc
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <getopt.h>
4 #include <sys/utsname.h>
5 #include <iostream>
6 #include <cstdio>
7 #include <fstream>
8 #include <set>
9 #include <apt-pkg/pkgcache.h>
10 #include <apt-pkg/cachefile.h>
11 #include <apt-pkg/dpkgdb.h>
12 #include <apt-pkg/progress.h>
13 #include <apt-pkg/init.h>
14 #include <apt-pkg/error.h>
15 #include <apt-pkg/algorithms.h>
16 #include <apt-pkg/sptr.h>
17 #include <apt-pkg/acquire-item.h>
18
19 #include "inapt.h"
20 #include "util.h"
21 #include "contrib/acqprogress.h"
22
23 char *prog = NULL;
24
25 static struct option opts[] = {
26     { "help", 0, NULL, 'h' },
27     { "simulate", 0, NULL, 's' },
28     { "profile", 0, NULL, 'p' },
29     { "update", 0, NULL, 'l' },
30     { "upgrade", 0, NULL, 'u' },
31     { "check", 0, NULL, 'c' },
32     { "purge", 0, NULL, '!' },
33     { "clean", 0, NULL, 'e' },
34     { "option", 0, NULL, 'o' },
35     { "strict", 0, NULL, 't' },
36     { NULL, 0, NULL, '\0' },
37 };
38
39 static bool run_install(pkgCacheFile &cache) {
40    if (_config->FindB("Inapt::Purge", false))
41       for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
42          if (!i.Purge() && cache[i].Mode == pkgDepCache::ModeDelete)
43             cache->MarkDelete(i, true);
44
45    if (cache->BrokenCount())
46        fatal("broken packages during install");
47
48    if (!cache->DelCount() && !cache->InstCount() && !cache->BadCount())
49       return true;
50
51    pkgRecords Recs (cache);
52    if (_error->PendingError())
53       return false;
54
55    FileFd Lock;
56    Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
57    if (_error->PendingError())
58        return _error->Error("Unable to lock the download directory");
59
60    pkgAcquire Fetcher;
61
62    unsigned int width = 80;
63    AcqTextStatus status (width, 0);
64    Fetcher.Setup(&status);
65
66    pkgSourceList List;
67    if (List.ReadMainList() == false)
68       return _error->Error("The list of sources could not be read");
69
70    SPtr<pkgPackageManager> PM = _system->CreatePM(cache);
71    if (PM->GetArchives(&Fetcher, &List, &Recs) == false ||
72        _error->PendingError())
73       return false;
74
75   if (Fetcher.Run() == pkgAcquire::Failed)
76      return false;
77
78   bool Failed = false;
79   for (pkgAcquire::ItemIterator i = Fetcher.ItemsBegin(); i != Fetcher.ItemsEnd(); i++) {
80      if ((*i)->Status != pkgAcquire::Item::StatDone || (*i)->Complete != true)
81          Failed = true;
82   }
83
84   if (Failed)
85      return _error->Error("Unable to fetch some archives");
86
87   _system->UnLock();
88
89   pkgPackageManager::OrderResult Res = PM->DoInstall(-1);
90   if (Res == pkgPackageManager::Completed)
91      return true;
92
93   return false;
94 }
95
96 static void run_autoremove(pkgCacheFile &cache) {
97     bool purge = _config->FindB("Inapt::Purge", false);
98
99     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
100         if (cache[i].Garbage) {
101             debug("autoremove: %s", i.Name());
102             cache->MarkDelete(i, purge);
103         }
104     }
105
106     if (cache->BrokenCount())
107         fatal("automatic removal broke packages");
108 }
109
110 static void usage() {
111     fprintf(stderr, "Usage: %s [options] [filename..]\n", prog);
112     exit(2);
113 }
114
115 static bool test_profile(const char *profile, std::set<std::string> *profiles) {
116     return (*profile != '!' && profiles->find(profile) != profiles->end())
117             || (*profile == '!' && profiles->find(profile + 1) == profiles->end());
118 }
119
120 static bool test_anyprofile(std::string &profile, std::set<std::string> *profiles) {
121     char *s = xstrdup(profile.c_str());
122     const char *c = strtok(s, "/");
123
124     if (test_profile(c, profiles)) {
125         free(s);
126         return true;
127     }
128
129     while ((c = strtok(NULL, "/")) != NULL) {
130         if (test_profile(c, profiles)) {
131             free(s);
132             return true;
133         }
134     }
135
136     free(s);
137     return false;
138 }
139
140 static pkgCache::PkgIterator eval_pkg(inapt_package *package, pkgCacheFile &cache) {
141     pkgCache::PkgIterator pkg;
142
143     for (std::vector<std::string>::iterator i = package->alternates.begin(); i != package->alternates.end(); i++) {
144         pkgCache::PkgIterator tmp = cache->FindPkg(*i);
145
146         /* no such package */
147         if (tmp.end())
148             continue;
149
150         /* real package */
151         if (cache[tmp].CandidateVer) {
152             pkg = tmp;
153             break;
154         }
155
156         /* virtual package */
157         if (tmp->ProvidesList) {
158             if (!tmp.ProvidesList()->NextProvides) {
159                 pkgCache::PkgIterator provide = tmp.ProvidesList().OwnerPkg();
160                 if (package->action == inapt_action::INSTALL) {
161                     debug("selecting %s instead of %s", provide.Name(), tmp.Name());
162                     pkg = provide;
163                     break;
164                 } else {
165                     debug("will not remove %s instead of virtual package %s", provide.Name(), tmp.Name());
166                 }
167             } else {
168                 debug("%s is a virtual package", tmp.Name());
169             }
170         } else {
171             debug("%s is a virtual packages with no provides", tmp.Name());
172         }
173     }
174
175     if (pkg.end()) {
176         if (package->alternates.size() == 1) {
177             if (_config->FindB("Inapt::Strict", false))
178                     _error->Error("%s:%d: No such package: %s", package->filename, package->linenum, package->alternates[0].c_str());
179             else
180                     _error->Warning("%s:%d: No such package: %s", package->filename, package->linenum, package->alternates[0].c_str());
181         } else {
182             std::vector<std::string>::iterator i = package->alternates.begin();
183             std::string message = *(i++);
184             while (i != package->alternates.end()) {
185                 message.append(", ").append(*(i++));
186             }
187             if (_config->FindB("Inapt::Strict", false))
188                     _error->Error("%s:%d: No alternative available: %s", package->filename, package->linenum, message.c_str());
189             else
190                     _error->Warning("%s:%d: No alternative available: %s", package->filename, package->linenum, message.c_str());
191         }
192     }
193
194     return pkg;
195 }
196
197 static bool test_profiles(vector<std::string> *test_profiles, std::set<std::string> *profiles) {
198     bool ok = true;
199     for (vector<std::string>::iterator j = test_profiles->begin(); j < test_profiles->end(); j++) {
200         if (!test_anyprofile(*j, profiles)) {
201             ok = false;
202             break;
203         }
204     }
205     return ok;
206 }
207
208 static void eval_action(inapt_action *action, std::set<std::string> *profiles, std::vector<inapt_package *> *final_actions) {
209     for (vector<inapt_package *>::iterator i = action->packages.begin(); i < action->packages.end(); i++) {
210         if (test_profiles(&(*i)->predicates, profiles))
211             final_actions->push_back(*i);
212     }
213 }
214
215 static void eval_block(inapt_block *block, std::set<std::string> *profiles, std::vector<inapt_package *> *final_actions) {
216     if (!block)
217         return;
218
219     for (vector<inapt_action *>::iterator i = block->actions.begin(); i < block->actions.end(); i++)
220         if (test_profiles(&(*i)->predicates, profiles))
221             eval_action(*i, profiles, final_actions);
222
223     for (vector<inapt_conditional *>::iterator i = block->children.begin(); i < block->children.end(); i++) {
224         if (test_profiles(&(*i)->predicates, profiles))
225             eval_block((*i)->then_block, profiles, final_actions);
226         else
227             eval_block((*i)->else_block, profiles, final_actions);
228     }
229 }
230
231 static void eval_profiles(inapt_block *block, std::set<std::string> *profiles) {
232     if (!block)
233         return;
234
235     for (vector<inapt_profiles *>::iterator i = block->profiles.begin(); i < block->profiles.end(); i++)
236         if (test_profiles(&(*i)->predicates, profiles))
237             for (vector<std::string>::iterator j = (*i)->profiles.begin(); j != (*i)->profiles.end(); j++)
238                 profiles->insert(*j);
239
240     for (vector<inapt_conditional *>::iterator i = block->children.begin(); i < block->children.end(); i++) {
241         if (test_profiles(&(*i)->predicates, profiles))
242             eval_profiles((*i)->then_block, profiles);
243         else
244             eval_profiles((*i)->else_block, profiles);
245     }
246 }
247
248 static void dump_nondownloadable(pkgCacheFile &cache) {
249     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
250        if (i.CurrentVer() && !i.CurrentVer().Downloadable())
251            debug("package %s version %s is not downloadable", i.Name(), i.CurrentVer().VerStr());
252 }
253
254 static void dump_actions(pkgCacheFile &cache) {
255     debug("inst %lu del %lu keep %lu broken %lu bad %lu",
256             cache->InstCount(), cache->DelCount(), cache->KeepCount(),
257             cache->BrokenCount(), cache->BadCount());
258     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
259        if (cache[i].Install())
260          debug("installing %s", i.Name());
261        if (cache[i].Delete())
262          debug("removing %s", i.Name());
263        if (cache[i].InstBroken())
264          debug("install broken %s", i.Name());
265        if (cache[i].NowBroken())
266          debug("now broken %s", i.Name());
267     }
268 }
269
270 static bool sanity_check(std::vector<inapt_package *> *final_actions, pkgCacheFile &cache) {
271     bool okay = true;
272     std::map<std::string, inapt_package *> packages;
273
274     for (vector<inapt_package *>::iterator i = final_actions->begin(); i != final_actions->end(); i++) {
275         if ((*i)->pkg.end())
276                 continue;
277         if (packages.find((*i)->pkg.Name()) != packages.end()) {
278             inapt_package *first = packages[(*i)->pkg.Name()];
279             inapt_package *current = *i;
280             _error->Error("Multiple directives for package %s at %s:%d and %s:%d",
281                     (*i)->pkg.Name(), first->filename, first->linenum, current->filename, current->linenum);
282             okay = false;
283             continue;
284         }
285         packages[(*i)->pkg.Name()] = *i;
286     }
287
288     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
289         if (cache[i].Delete() && (i->Flags & pkgCache::Flag::Essential || i->Flags & pkgCache::Flag::Important)) {
290             _error->Error("Removing essential package %s", i.Name());
291             okay = false;
292         }
293     }
294
295     return okay;
296 }
297
298 static void show_breakage(pkgCacheFile &cache) {
299     std::string broken;
300     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
301         if (cache[i].NowBroken() || cache[i].InstBroken())
302             broken.append(" ").append(i.Name());
303
304     _error->Error("Broken packages:%s", broken.c_str());
305 }
306
307 static void exec_actions(std::vector<inapt_package *> *final_actions) {
308     int marked = 0;
309     bool purge = _config->FindB("Inapt::Purge", false);
310
311     pkgInitConfig(*_config);
312     pkgInitSystem(*_config, _system);
313
314     OpTextProgress prog;
315     pkgCacheFile cache;
316
317     if (cache.Open(&prog, true) == false)
318         return;
319
320     pkgDepCache::ActionGroup group (cache);
321
322     for (vector<inapt_package *>::iterator i = final_actions->begin(); i != final_actions->end(); i++)
323         (*i)->pkg = eval_pkg(*i, cache);
324
325     if (_error->PendingError())
326         return;
327     _error->DumpErrors();
328
329     // preliminary loop (auto-installs, includes recommends - could do this manually)
330     for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++) {
331         pkgCache::PkgIterator k = (*i)->pkg;
332         if (k.end())
333                 continue;
334         switch ((*i)->action) {
335             case inapt_action::INSTALL:
336                 if (!k.CurrentVer() || cache[k].Delete()) {
337                     debug("install %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
338                     cache->MarkInstall(k, true);
339                 }
340                 break;
341             case inapt_action::REMOVE:
342                 break;
343             default:
344                 fatal("uninitialized action");
345         }
346     }
347
348     // secondary loop (removes package and reinstalls auto-removed packages)
349     for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++) {
350         pkgCache::PkgIterator k = (*i)->pkg;
351         if (k.end())
352                 continue;
353         switch ((*i)->action) {
354             case inapt_action::INSTALL:
355                 if ((!k.CurrentVer() && !cache[k].Install()) || cache[k].Delete()) {
356                     debug("force install %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
357                     cache->MarkInstall(k, false);
358                 }
359                 if (cache[k].Flags & pkgCache::Flag::Auto) {
360                     debug("marking %s as manually installed", (*i)->pkg.Name());
361                     cache->MarkAuto(k, false);
362                     marked++;
363                 }
364                 break;
365             case inapt_action::REMOVE:
366                 if ((k.CurrentVer() && !cache[k].Delete()) || cache[k].Install())
367                     debug("remove %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
368
369                 /* always mark so purge works */
370                 cache->MarkDelete(k, purge);
371                 break;
372             default:
373                 fatal("uninitialized action");
374         }
375     }
376
377     if (_error->PendingError())
378         return;
379
380     dump_nondownloadable(cache);
381     dump_actions(cache);
382
383     if (cache->BrokenCount()) {
384         pkgProblemResolver fix (cache);
385         for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++)
386             fix.Protect((*i)->pkg);
387         fix.Resolve();
388
389         if (cache->BrokenCount()) {
390             show_breakage(cache);
391             return;
392         }
393     }
394
395     cache->MarkAndSweep();
396     run_autoremove(cache);
397
398     if (!sanity_check(final_actions, cache))
399         return;
400
401     if (_config->FindB("Inapt::Simulate", false)) {
402         pkgSimulate PM (cache);
403         PM.DoInstall(-1);
404         return;
405     }
406
407     run_install(cache);
408     if (_error->PendingError())
409         return;
410
411     if (marked) {
412         if (_config->FindB("Inapt::Simulate", false)) {
413             debug("marked %d packages", marked);
414         } else {
415             debug("marked %d packages, writing state file", marked);
416             cache->writeStateFile(NULL);
417         }
418     }
419 }
420
421 static void debug_profiles(std::set<std::string> *profiles) {
422     std::string s = "profiles:";
423
424     for (std::set<std::string>::iterator i = profiles->begin(); i != profiles->end(); i++) {
425         s.append(" ");
426         s.append(*i);
427     }
428
429     debug("%s", s.c_str());
430 }
431
432 static void auto_profiles(std::set<std::string> *profiles) {
433     struct utsname uts;
434     if (uname(&uts))
435         fatalpe("uname");
436     profiles->insert(uts.nodename);
437 }
438
439 static void set_option(char *opt) {
440     char *eq = strchr(opt, '=');
441     if (!eq)
442         fatal("invalid syntax for '%s': must be <option>=<value>", opt);
443
444     std::string option (opt, eq - opt);
445     std::string value (eq + 1);
446
447     _config->Set(option, value);
448 }
449
450 int main(int argc, char *argv[]) {
451     int opt;
452
453     std::set<std::string> profiles;
454
455     prog = xstrdup(basename(argv[0]));
456     while ((opt = getopt_long(argc, argv, "?hp:slucedo:", opts, NULL)) != -1) {
457         switch (opt) {
458             case '?':
459             case 'h':
460                 usage();
461                 break;
462             case 'p':
463                 profiles.insert(optarg);
464                 break;
465             case 's':
466                 _config->Set("Inapt::Simulate", true);
467                 break;
468             case '!':
469                 _config->Set("Inapt::Purge", true);
470                 break;
471             case 'l':
472                 _config->Set("Inapt::Update", true);
473                 break;
474             case 'u':
475                 _config->Set("Inapt::Upgrade", true);
476                 break;
477             case 'c':
478                 _config->Set("Inapt::Check", true);
479                 break;
480             case 'e':
481                 _config->Set("Inapt::Clean", true);
482                 break;
483             case 't':
484                 _config->Set("Inapt::Strict", true);
485                 break;
486             case 'd':
487                 debug_level++;
488                 break;
489             case 'o':
490                 set_option(optarg);
491                 break;
492             default:
493                 fatal("error parsing arguments");
494         }
495     }
496
497     int num_files = argc - optind;
498
499     inapt_block context;
500     std::vector<inapt_package *> final_actions;
501
502     if (!num_files)
503         parser(NULL, &context);
504
505     while (num_files--)
506         parser(argv[optind++], &context);
507
508     auto_profiles(&profiles);
509     eval_profiles(&context, &profiles);
510     debug_profiles(&profiles);
511     eval_block(&context, &profiles, &final_actions);
512     exec_actions(&final_actions);
513
514     if (_error->PendingError()) {
515         _error->DumpErrors();
516         exit(1);
517     }
518
519     return 0;
520 }