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