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