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