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