Fix some deprecation warnings
[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    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             _error->Error("%s:%d: No such package: %s", package->filename, package->linenum, package->alternates[0].c_str());
177         } else {
178             std::vector<std::string>::iterator i = package->alternates.begin();
179             std::string message = *(i++);
180             while (i != package->alternates.end()) {
181                 message.append(", ").append(*(i++));
182             }
183             _error->Error("%s:%d: No alternative available: %s", package->filename, package->linenum, message.c_str());
184         }
185     }
186
187     return pkg;
188 }
189
190 static bool test_profiles(vector<std::string> *test_profiles, std::set<std::string> *profiles) {
191     bool ok = true;
192     for (vector<std::string>::iterator j = test_profiles->begin(); j < test_profiles->end(); j++) {
193         if (!test_anyprofile(*j, profiles)) {
194             ok = false;
195             break;
196         }
197     }
198     return ok;
199 }
200
201 static void eval_action(inapt_action *action, std::set<std::string> *profiles, std::vector<inapt_package *> *final_actions) {
202     for (vector<inapt_package *>::iterator i = action->packages.begin(); i < action->packages.end(); i++) {
203         if (test_profiles(&(*i)->predicates, profiles))
204             final_actions->push_back(*i);
205     }
206 }
207
208 static void eval_block(inapt_block *block, std::set<std::string> *profiles, std::vector<inapt_package *> *final_actions) {
209     if (!block)
210         return;
211
212     for (vector<inapt_action *>::iterator i = block->actions.begin(); i < block->actions.end(); i++)
213         if (test_profiles(&(*i)->predicates, profiles))
214             eval_action(*i, profiles, final_actions);
215
216     for (vector<inapt_conditional *>::iterator i = block->children.begin(); i < block->children.end(); i++) {
217         if (test_profiles(&(*i)->predicates, profiles))
218             eval_block((*i)->then_block, profiles, final_actions);
219         else
220             eval_block((*i)->else_block, profiles, final_actions);
221     }
222 }
223
224 static void eval_profiles(inapt_block *block, std::set<std::string> *profiles) {
225     if (!block)
226         return;
227
228     for (vector<inapt_profiles *>::iterator i = block->profiles.begin(); i < block->profiles.end(); i++)
229         if (test_profiles(&(*i)->predicates, profiles))
230             for (vector<std::string>::iterator j = (*i)->profiles.begin(); j != (*i)->profiles.end(); j++)
231                 profiles->insert(*j);
232
233     for (vector<inapt_conditional *>::iterator i = block->children.begin(); i < block->children.end(); i++) {
234         if (test_profiles(&(*i)->predicates, profiles))
235             eval_profiles((*i)->then_block, profiles);
236         else
237             eval_profiles((*i)->else_block, profiles);
238     }
239 }
240
241 static void dump_nondownloadable(pkgCacheFile &cache) {
242     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
243        if (i.CurrentVer() && !i.CurrentVer().Downloadable())
244            debug("package %s version %s is not downloadable", i.Name(), i.CurrentVer().VerStr());
245 }
246
247 static void dump_actions(pkgCacheFile &cache) {
248     debug("inst %lu del %lu keep %lu broken %lu bad %lu",
249             cache->InstCount(), cache->DelCount(), cache->KeepCount(),
250             cache->BrokenCount(), cache->BadCount());
251     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
252        if (cache[i].Install())
253          debug("installing %s", i.Name());
254        if (cache[i].Delete())
255          debug("removing %s", i.Name());
256        if (cache[i].InstBroken())
257          debug("install broken %s", i.Name());
258        if (cache[i].NowBroken())
259          debug("now broken %s", i.Name());
260     }
261 }
262
263 static bool sanity_check(std::vector<inapt_package *> *final_actions, pkgCacheFile &cache) {
264     bool okay = true;
265     std::map<std::string, inapt_package *> packages;
266
267     for (vector<inapt_package *>::iterator i = final_actions->begin(); i != final_actions->end(); i++) {
268         if (packages.find((*i)->pkg.Name()) != packages.end()) {
269             inapt_package *first = packages[(*i)->pkg.Name()];
270             inapt_package *current = *i;
271             _error->Error("Multiple directives for package %s at %s:%d and %s:%d",
272                     (*i)->pkg.Name(), first->filename, first->linenum, current->filename, current->linenum);
273             okay = false;
274             continue;
275         }
276         packages[(*i)->pkg.Name()] = *i;
277     }
278
279     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
280         if (cache[i].Delete() && (i->Flags & pkgCache::Flag::Essential || i->Flags & pkgCache::Flag::Important)) {
281             _error->Error("Removing essential package %s", i.Name());
282             okay = false;
283         }
284     }
285
286     return okay;
287 }
288
289 static void show_breakage(pkgCacheFile &cache) {
290     std::string broken;
291     for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
292         if (cache[i].NowBroken() || cache[i].InstBroken())
293             broken.append(" ").append(i.Name());
294
295     _error->Error("Broken packages:%s", broken.c_str());
296 }
297
298 static void exec_actions(std::vector<inapt_package *> *final_actions) {
299     int marked = 0;
300     bool purge = _config->FindB("Inapt::Purge", false);
301
302     pkgInitConfig(*_config);
303     pkgInitSystem(*_config, _system);
304
305     OpTextProgress prog;
306     pkgCacheFile cache;
307
308     if (cache.Open(&prog, true) == false)
309         return;
310
311     pkgDepCache::ActionGroup group (cache);
312
313     for (vector<inapt_package *>::iterator i = final_actions->begin(); i != final_actions->end(); i++)
314         (*i)->pkg = eval_pkg(*i, cache);
315
316     if (_error->PendingError())
317         return;
318
319     // preliminary loop (auto-installs, includes recommends - could do this manually)
320     for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++) {
321         pkgCache::PkgIterator k = (*i)->pkg;
322         switch ((*i)->action) {
323             case inapt_action::INSTALL:
324                 if (!k.CurrentVer() || cache[k].Delete()) {
325                     debug("install %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
326                     cache->MarkInstall(k, true);
327                 }
328                 break;
329             case inapt_action::REMOVE:
330                 break;
331             default:
332                 fatal("uninitialized action");
333         }
334     }
335
336     // secondary loop (removes package and reinstalls auto-removed packages)
337     for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++) {
338         pkgCache::PkgIterator k = (*i)->pkg;
339         switch ((*i)->action) {
340             case inapt_action::INSTALL:
341                 if ((!k.CurrentVer() && !cache[k].Install()) || cache[k].Delete()) {
342                     debug("force install %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
343                     cache->MarkInstall(k, false);
344                 }
345                 if (cache[k].Flags & pkgCache::Flag::Auto) {
346                     debug("marking %s as manually installed", (*i)->pkg.Name());
347                     cache->MarkAuto(k, false);
348                     marked++;
349                 }
350                 break;
351             case inapt_action::REMOVE:
352                 if ((k.CurrentVer() && !cache[k].Delete()) || cache[k].Install())
353                     debug("remove %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
354
355                 /* always mark so purge works */
356                 cache->MarkDelete(k, purge);
357                 break;
358             default:
359                 fatal("uninitialized action");
360         }
361     }
362
363     if (_error->PendingError())
364         return;
365
366     dump_nondownloadable(cache);
367     dump_actions(cache);
368
369     if (cache->BrokenCount()) {
370         pkgProblemResolver fix (cache);
371         for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++)
372             fix.Protect((*i)->pkg);
373         fix.Resolve();
374
375         if (cache->BrokenCount()) {
376             show_breakage(cache);
377             return;
378         }
379     }
380
381     cache->MarkAndSweep();
382     run_autoremove(cache);
383
384     if (!sanity_check(final_actions, cache))
385         return;
386
387     if (_config->FindB("Inapt::Simulate", false)) {
388         pkgSimulate PM (cache);
389         PM.DoInstall(-1);
390         return;
391     }
392
393     run_install(cache);
394     if (_error->PendingError())
395         return;
396
397     if (marked) {
398         if (_config->FindB("Inapt::Simulate", false)) {
399             debug("marked %d packages", marked);
400         } else {
401             debug("marked %d packages, writing state file", marked);
402             cache->writeStateFile(NULL);
403         }
404     }
405 }
406
407 static void debug_profiles(std::set<std::string> *profiles) {
408     std::string s = "profiles:";
409
410     for (std::set<std::string>::iterator i = profiles->begin(); i != profiles->end(); i++) {
411         s.append(" ");
412         s.append(*i);
413     }
414
415     debug("%s", s.c_str());
416 }
417
418 static void auto_profiles(std::set<std::string> *profiles) {
419     struct utsname uts;
420     if (uname(&uts))
421         fatalpe("uname");
422     profiles->insert(uts.nodename);
423 }
424
425 static void set_option(char *opt) {
426     char *eq = strchr(opt, '=');
427     if (!eq)
428         fatal("invalid syntax for '%s': must be <option>=<value>", opt);
429
430     std::string option (opt, eq - opt);
431     std::string value (eq + 1);
432
433     _config->Set(option, value);
434 }
435
436 int main(int argc, char *argv[]) {
437     int opt;
438
439     std::set<std::string> profiles;
440
441     prog = xstrdup(basename(argv[0]));
442     while ((opt = getopt_long(argc, argv, "?hp:slucedo:", opts, NULL)) != -1) {
443         switch (opt) {
444             case '?':
445             case 'h':
446                 usage();
447                 break;
448             case 'p':
449                 profiles.insert(optarg);
450                 break;
451             case 's':
452                 _config->Set("Inapt::Simulate", true);
453                 break;
454             case '!':
455                 _config->Set("Inapt::Purge", true);
456                 break;
457             case 'l':
458                 _config->Set("Inapt::Update", true);
459                 break;
460             case 'u':
461                 _config->Set("Inapt::Upgrade", true);
462                 break;
463             case 'c':
464                 _config->Set("Inapt::Check", true);
465                 break;
466             case 'e':
467                 _config->Set("Inapt::Clean", true);
468                 break;
469             case 'd':
470                 debug_level++;
471                 break;
472             case 'o':
473                 set_option(optarg);
474                 break;
475             default:
476                 fatal("error parsing arguments");
477         }
478     }
479
480     int num_files = argc - optind;
481
482     inapt_block context;
483     std::vector<inapt_package *> final_actions;
484
485     if (!num_files)
486         parser(NULL, &context);
487
488     while (num_files--)
489         parser(argv[optind++], &context);
490
491     auto_profiles(&profiles);
492     eval_profiles(&context, &profiles);
493     debug_profiles(&profiles);
494     eval_block(&context, &profiles, &final_actions);
495     exec_actions(&final_actions);
496
497     if (_error->PendingError()) {
498         _error->DumpErrors();
499         exit(1);
500     }
501
502     return 0;
503 }