Add support for simulating installation
[mspang/inapt.git] / inapt.cc
index aa36ad2..984ca8a 100644 (file)
--- a/inapt.cc
+++ b/inapt.cc
@@ -1,8 +1,11 @@
 #include <stdio.h>
 #include <stdlib.h>
+#include <getopt.h>
+#include <sys/utsname.h>
 #include <iostream>
 #include <cstdio>
 #include <fstream>
+#include <set>
 #include <apt-pkg/pkgcache.h>
 #include <apt-pkg/cachefile.h>
 #include <apt-pkg/dpkgdb.h>
 #include "util.h"
 #include "acqprogress.h"
 
-using namespace std;
+char *prog = NULL;
 
-bool InstallPackages(pkgCacheFile &Cache,bool ShwKept = false,bool Ask = true,
-                     bool Safety = true)
-{
-   if (_config->FindB("APT::Get::Purge",false) == true)
-   {
-      pkgCache::PkgIterator I = Cache->PkgBegin();
-      for (; I.end() == false; I++)
-      {
-         if (I.Purge() == false && Cache[I].Mode == pkgDepCache::ModeDelete)
-            Cache->MarkDelete(I,true);
-      }
-   }
-
-   if (Cache->BrokenCount() != 0)
-   {
-      return _error->Error("Internal error, InstallPackages was called with broken packages!");
-   }
-
-   if (Cache->DelCount() == 0 && Cache->InstCount() == 0 &&
-       Cache->BadCount() == 0)
-      return true;
+static struct option opts[] = {
+    { "auto-remove", 0, NULL, 'z' },
+    { "simulate", 0, NULL, 's' },
+    { "purge", 0, NULL, 'u' },
+    { NULL, 0, NULL, '\0' },
+};
 
-   if (Cache->DelCount() != 0 && _config->FindB("APT::Get::Remove",true) == false)
-      return _error->Error(("Packages need to be removed but remove is disabled."));
+bool run_install(pkgCacheFile &cache) {
+   if (_config->FindB("Inapt::Purge", false))
+      for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
+         if (!i.Purge() && cache[i].Mode == pkgDepCache::ModeDelete)
+            cache->MarkDelete(i, true);
 
-   pkgRecords Recs(Cache);
-   if (_error->PendingError() == true)
+   if (cache->BrokenCount())
+       fatal("broken packages during install");
+
+   if (!cache->DelCount() && !cache->InstCount() && !cache->BadCount())
+      return true;
+
+   pkgRecords Recs (cache);
+   if (_error->PendingError())
       return false;
 
    FileFd Lock;
-   if (_config->FindB("Debug::NoLocking",false) == false &&
-       _config->FindB("APT::Get::Print-URIs") == false)
-   {
-      Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
-      if (_error->PendingError() == true)
-         return _error->Error(("Unable to lock the download directory"));
-   }
+   Lock.Fd(GetLock(_config->FindDir("Dir::Cache::Archives") + "lock"));
+   if (_error->PendingError())
+       return _error->Error(("Unable to lock the download directory"));
 
    unsigned int width = 80;
    AcqTextStatus status (width, 0);
@@ -65,143 +58,215 @@ bool InstallPackages(pkgCacheFile &Cache,bool ShwKept = false,bool Ask = true,
    if (List.ReadMainList() == false)
       return _error->Error(("The list of sources could not be read."));
 
-   SPtr<pkgPackageManager> PM= _system->CreatePM(Cache);
+   SPtr<pkgPackageManager> PM= _system->CreatePM(cache);
    if (PM->GetArchives(&Fetcher, &List, &Recs) == false ||
-       _error->PendingError() == true)
+       _error->PendingError())
       return false;
 
-   if (_error->PendingError() == true)
-      return false;
+  if (Fetcher.Run() == pkgAcquire::Failed)
+     return false;
 
-   while (1)
-   {
-      bool Transient = false;
-      if (_config->FindB("APT::Get::Download",true) == false)
-      {
-         for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I < Fetcher.ItemsEnd();)
-         {
-            if ((*I)->Local == true)
-            {
-               I++;
-               continue;
-            }
+  bool Failed = false;
+  for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I != Fetcher.ItemsEnd(); I++)
+  {
+     if ((*I)->Status != pkgAcquire::Item::StatDone || (*I)->Complete != true) {
+         fprintf(stderr,("Failed to fetch %s  %s\n"),(*I)->DescURI().c_str(),
+                 (*I)->ErrorText.c_str());
+         Failed = true;
+     }
+  }
 
-            // Close the item and check if it was found in cache
-            (*I)->Finished();
-            if ((*I)->Complete == false)
-               Transient = true;
-
-            // Clear it out of the fetch list
-            delete *I;
-            I = Fetcher.ItemsBegin();
-         }
-      }
-
-      if (Fetcher.Run() == pkgAcquire::Failed)
-         return false;
-
-      // Print out errors
-      bool Failed = false;
-      for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I != Fetcher.ItemsEnd(); I++)
-      {
-         if ((*I)->Status == pkgAcquire::Item::StatDone &&
-             (*I)->Complete == true)
-            continue;
+  if (Failed)
+     return _error->Error("Unable to fetch some archives");
+
+  _system->UnLock();
+
+  pkgPackageManager::OrderResult Res = PM->DoInstall(-1);
+  if (Res == pkgPackageManager::Completed)
+     return true;
+
+  return false;
+}
+
+void run_autoremove(pkgCacheFile &cache)
+{
+    for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
+        if (cache[i].Garbage) {
+            debug("autoremove: %s", i.Name());
+            cache->MarkDelete(i, 0);
+        }
+    }
+
+    if (cache->BrokenCount())
+        fatal("automatic removal broke packages");
+}
+
+static void usage() {
+    fprintf(stderr, "Usage: %s [filename..]\n", prog);
+    exit(2);
+}
+
+static bool test_macro(const char *macro, std::set<std::string> *defines) {
+    return (*macro != '!' && defines->find(macro) != defines->end())
+            || (*macro == '!' && defines->find(macro + 1) == defines->end());
+}
 
-         if ((*I)->Status == pkgAcquire::Item::StatIdle)
-         {
-            Transient = true;
-            // Failed = true;
+static pkgCache::PkgIterator eval_pkg(inapt_package *package, pkgCacheFile &cache) {
+    pkgCache::PkgIterator pkg;
+
+    for (std::vector<std::string>::iterator i = package->alternates.begin(); i != package->alternates.end(); i++) {
+        pkgCache::PkgIterator tmp = cache->FindPkg(*i);
+
+        /* no such package */
+        if (tmp.end())
             continue;
-         }
 
-         fprintf(stderr,("Failed to fetch %s  %s\n"),(*I)->DescURI().c_str(),
-                 (*I)->ErrorText.c_str());
-         Failed = true;
-      }
-
-      /* If we are in no download mode and missing files and there were
-         'failures' then the user must specify -m. Furthermore, there
-         is no such thing as a transient error in no-download mode! */
-      if (Transient == true &&
-          _config->FindB("APT::Get::Download",true) == false)
-      {
-         Transient = false;
-         Failed = true;
-      }
-
-      if (_config->FindB("APT::Get::Download-Only",false) == true)
-      {
-         if (Failed == true && _config->FindB("APT::Get::Fix-Missing",false) == false)
-            return _error->Error(("Some files failed to download"));
-         //c1out << _("Download complete and in download only mode") << endl;
-         return true;
-      }
-
-      if (Failed == true && _config->FindB("APT::Get::Fix-Missing",false) == false)
-      {
-         return _error->Error(("Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?"));
-      }
-
-      if (Transient == true && Failed == true)
-         return _error->Error(("--fix-missing and media swapping is not currently supported"));
-
-      // Try to deal with missing package files
-      if (Failed == true && PM->FixMissing() == false)
-      {
-         cerr << ("Unable to correct missing packages.") << endl;
-         return _error->Error(("Aborting install."));
-      }
-
-      _system->UnLock();
-      int status_fd = _config->FindI("APT::Status-Fd",-1);
-      pkgPackageManager::OrderResult Res = PM->DoInstall(status_fd);
-      if (Res == pkgPackageManager::Failed || _error->PendingError() == true)
-         return false;
-      if (Res == pkgPackageManager::Completed)
-         return true;
-
-      // Reload the fetcher object and loop again for media swapping
-      Fetcher.Shutdown();
-      if (PM->GetArchives(&Fetcher,&List,&Recs) == false)
-         return false;
-
-      _system->Lock();
-   }
+        /* real package */
+        if (cache[tmp].CandidateVer) {
+            pkg = tmp;
+            break;
+        }
+
+        /* virtual package */
+        if (tmp->ProvidesList) {
+            if (!tmp.ProvidesList()->NextProvides) {
+                pkgCache::PkgIterator provide = tmp.ProvidesList().OwnerPkg();
+                if (package->action == inapt_action::INSTALL) {
+                    debug("selecting %s instead of %s", provide.Name(), tmp.Name());
+                    pkg = provide;
+                    break;
+                } else {
+                    debug("will not remove %s instead of virtual package %s", provide.Name(), tmp.Name());
+                }
+            } else {
+                debug("%s is a virtual package", tmp.Name());
+            }
+        } else {
+            debug("%s is a virtual packages with no provides", tmp.Name());
+        }
+    }
+
+    if (pkg.end()) {
+        if (package->alternates.size() == 1) {
+            _error->Error("%s:%d: No such package: %s", package->filename, package->linenum, package->alternates[0].c_str());
+        } else {
+            std::vector<std::string>::iterator i = package->alternates.begin();
+            std::string message = *(i++);
+            while (i != package->alternates.end()) {
+                message.append(", ");
+                message.append(*(i++));
+            }
+            _error->Error("%s:%d: No alternative available: %s", package->filename, package->linenum, message.c_str());
+        }
+    }
+
+    return pkg;
 }
 
-int main(int argc, char *argv[]) {
+static bool test_macros(vector<std::string> *macros, std::set<std::string> *defines) {
+    bool ok = true;
+    for (vector<std::string>::iterator j = macros->begin(); j < macros->end(); j++) {
+        if (!test_macro((*j).c_str(), defines)) {
+            ok = false;
+            break;
+        }
+    }
+    return ok;
+}
+
+static void eval_action(inapt_action *action, std::set<std::string> *defines, std::vector<inapt_package *> *final_actions) {
+    for (vector<inapt_package *>::iterator i = action->packages.begin(); i < action->packages.end(); i++) {
+        if (test_macros(&(*i)->predicates, defines))
+            final_actions->push_back(*i);
+    }
+}
+
+static void eval_block(inapt_block *block, std::set<std::string> *defines, std::vector<inapt_package *> *final_actions) {
+    if (!block)
+        return;
+
+    for (vector<inapt_action *>::iterator i = block->actions.begin(); i < block->actions.end(); i++)
+        if (test_macros(&(*i)->predicates, defines))
+            eval_action(*i, defines, final_actions);
 
-    vector<inapt_action> actions;
+    for (vector<inapt_conditional *>::iterator i = block->children.begin(); i < block->children.end(); i++) {
+        if (test_macro((*i)->condition, defines))
+            eval_block((*i)->then_block, defines, final_actions);
+        else
+            eval_block((*i)->else_block, defines, final_actions);
+    }
+}
+
+static void eval_profiles(inapt_block *block, std::set<std::string> *defines) {
+    if (!block)
+        return;
+
+    for (vector<inapt_profiles *>::iterator i = block->profiles.begin(); i < block->profiles.end(); i++)
+        if (test_macros(&(*i)->predicates, defines))
+            for (vector<std::string>::iterator j = (*i)->profiles.begin(); j != (*i)->profiles.end(); j++)
+                defines->insert(*j);
+
+    for (vector<inapt_conditional *>::iterator i = block->children.begin(); i < block->children.end(); i++) {
+        if (test_macro((*i)->condition, defines))
+            eval_profiles((*i)->then_block, defines);
+        else
+            eval_profiles((*i)->else_block, defines);
+    }
+}
+
+static void dump_nondownloadable(pkgCacheFile &cache) {
+    for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++)
+       if (i.CurrentVer() && !i.CurrentVer().Downloadable())
+           debug("package %s version %s is not downloadable", i.Name(), i.CurrentVer().VerStr());
+}
+
+static void dump_actions(pkgCacheFile &cache) {
+    debug("inst %lu del %lu keep %lu broken %lu bad %lu",
+                   cache->InstCount(), cache->DelCount(), cache->KeepCount(),
+                   cache->BrokenCount(), cache->BadCount());
+    for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
+       if (cache[i].Install())
+         debug("installing %s", i.Name());
+       if (cache[i].Delete())
+         debug("removing %s", i.Name());
+       if (cache[i].InstBroken())
+         debug("install broken %s", i.Name());
+       if (cache[i].NowBroken())
+         debug("now broken %s", i.Name());
+    }
+}
+
+static void exec_actions(std::vector<inapt_package *> *final_actions) {
+    int marked = 0;
 
     pkgInitConfig(*_config);
     pkgInitSystem(*_config, _system);
 
-     _config->Set("Debug::pkgProblemResolver", true);
+//     _config->Set("Debug::pkgProblemResolver", true);
+//     _config->Set("Debug::pkgAutoRemove", true);
 
     OpTextProgress prog;
-    pkgCacheFile cachef;
+    pkgCacheFile cache;
 
-    if (cachef.Open(prog) == false) {
-       _error->DumpErrors();
-        exit(1);
-    }
+    if (cache.Open(prog) == false)
+        return;
 
-    pkgCache *cache = cachef;
-    pkgDepCache *DCache = cachef;
+    pkgDepCache::ActionGroup group (cache);
 
-    scanner(&actions);
+    for (vector<inapt_package *>::iterator i = final_actions->begin(); i != final_actions->end(); i++)
+        (*i)->pkg = eval_pkg(*i, cache);
 
-    for (vector<inapt_action>::iterator i = actions.begin(); i < actions.end(); i++) {
-        pkgCache::PkgIterator pkg = cache->FindPkg(i->package);
-        if (pkg.end())
-            fatal("%s:%d: No such package: %s", i->filename, i->linenum, i->package);
-    }
+    if (_error->PendingError())
+        return;
 
-    for (vector<inapt_action>::iterator i = actions.begin(); i < actions.end(); i++) {
-        switch(i->action) {
+    for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++) {
+        pkgCache::PkgIterator k = (*i)->pkg;
+        switch ((*i)->action) {
             case inapt_action::INSTALL:
-                DCache->MarkInstall(cache->FindPkg(i->package), true);
+                if (!k.CurrentVer() || cache[k].Delete()) {
+                    debug("install %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
+                    cache->MarkInstall(k, true);
+                }
                 break;
             case inapt_action::REMOVE:
                 break;
@@ -210,70 +275,136 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    for (vector<inapt_action>::iterator i = actions.begin(); i < actions.end(); i++) {
-        switch(i->action) {
+    for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++) {
+        pkgCache::PkgIterator k = (*i)->pkg;
+        switch ((*i)->action) {
             case inapt_action::INSTALL:
-                printf("install %s %s:%d\n", i->package, i->filename, i->linenum);
-                DCache->MarkInstall(cache->FindPkg(i->package), false);
+                if ((!k.CurrentVer() && !cache[k].Install()) || cache[k].Delete()) {
+                    debug("install %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
+                    cache->MarkInstall(k, false);
+                }
+                if (cache[k].Flags & pkgCache::Flag::Auto) {
+                    marked++;
+                    debug("marking %s as manually installed", (*i)->pkg.Name());
+                    cache->MarkAuto(k, false);
+                }
                 break;
             case inapt_action::REMOVE:
-                printf("remove %s %s:%d\n", i->package, i->filename, i->linenum);
-                DCache->MarkDelete(cache->FindPkg(i->package), false);
+                if ((k.CurrentVer() && !cache[k].Delete()) || cache[k].Install()) {
+                    debug("remove %s %s:%d", (*i)->pkg.Name(), (*i)->filename, (*i)->linenum);
+                    cache->MarkDelete(k, false);
+                }
                 break;
             default:
                 fatal("uninitialized action");
         }
     }
 
-    fprintf(stderr, "\n");
-    fprintf(stderr, "nondownloadable packages:\n");
-    for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
-       if (i.CurrentVer() && !i.CurrentVer().Downloadable()) {
-              fprintf(stderr, "%s ", i.Name());
-              fprintf(stderr, "%s\n", DCache->GetCandidateVer(i).VerStr());
-       }
+    dump_nondownloadable(cache);
+    dump_actions(cache);
+
+    if (cache->BrokenCount()) {
+        pkgProblemResolver fix (cache);
+        for (vector<inapt_package *>::iterator i = final_actions->begin(); i < final_actions->end(); i++)
+            fix.Protect((*i)->pkg);
+        fix.Resolve();
+
+       if (_error->PendingError())
+          return;
+
+        dump_actions(cache);
     }
 
-    fprintf(stderr, "\n");
-    fprintf(stderr, "inst %lu del %lu keep %lu broken %lu bad %lu\n",
-                   DCache->InstCount(), DCache->DelCount(), DCache->KeepCount(),
-                   DCache->BrokenCount(), DCache->BadCount());
+    if (_config->FindB("Inapt::AutomaticRemove", false)) {
+        cache->MarkAndSweep();
+        run_autoremove(cache);
+    }
 
-    for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
-       if ((*DCache)[i].Install())
-         fprintf(stderr, "inst %s\n", i.Name());
-       if ((*DCache)[i].InstBroken())
-         fprintf(stderr, "instbroken %s\n", i.Name());
-       if ((*DCache)[i].NowBroken())
-         fprintf(stderr, "nowbroken %s\n", i.Name());
+    if (_config->FindB("Inapt::Simulate", false)) {
+        pkgSimulate PM (cache);
+        PM.DoInstall(-1);
+        return;
     }
 
-    fprintf(stderr, "\n");
+    run_install(cache);
+    if (_error->PendingError())
+        return;
 
-    pkgProblemResolver fix (DCache);
+    if (marked) {
+        if (_config->FindB("Inapt::Simulate", false)) {
+            debug("marked %d packages", marked);
+        } else {
+            debug("marked %d packages, writing state file", marked);
+            cache->writeStateFile(NULL);
+        }
+    }
+}
 
-    for (vector<inapt_action>::iterator i = actions.begin(); i < actions.end(); i++)
-           fix.Protect(cache->FindPkg(i->package));
-    for (vector<inapt_action>::iterator i = actions.begin(); i < actions.end(); i++)
-           fix.Protect(cache->FindPkg(i->package));
-    fix.Resolve();
+static void debug_profiles(std::set<std::string> *defines) {
+    std::string profiles = "profiles:";
 
-    fprintf(stderr, "\n");
-    fprintf(stderr, "inst %lu del %lu keep %lu broken %lu bad %lu\n",
-                   DCache->InstCount(), DCache->DelCount(), DCache->KeepCount(),
-                   DCache->BrokenCount(), DCache->BadCount());
-    for (pkgCache::PkgIterator i = cache->PkgBegin(); !i.end(); i++) {
-       if ((*DCache)[i].Install())
-         fprintf(stderr, "inst %s\n", i.Name());
-       if ((*DCache)[i].Delete())
-         fprintf(stderr, "del %s\n", i.Name());
-       if ((*DCache)[i].InstBroken())
-         fprintf(stderr, "instbroken %s\n", i.Name());
-       if ((*DCache)[i].NowBroken())
-         fprintf(stderr, "nowbroken %s\n", i.Name());
+    for (std::set<std::string>::iterator i = defines->begin(); i != defines->end(); i++) {
+        profiles.append(" ");
+        profiles.append(*i);
     }
 
-    fprintf(stderr, "\n");
+    debug("%s", profiles.c_str());
+}
+
+static void auto_profiles(std::set<std::string> *defines) {
+    struct utsname uts;
+    if (uname(&uts))
+        fatalpe("uname");
+    defines->insert(uts.nodename);
+}
+
+int main(int argc, char *argv[]) {
+    int opt;
 
-    InstallPackages(cachef);
+    std::set<std::string> defines;
+
+    prog = xstrdup(basename(argv[0]));
+    while ((opt = getopt_long(argc, argv, "p:s", opts, NULL)) != -1) {
+        switch (opt) {
+            case 'p':
+                defines.insert(optarg);
+                break;
+            case '?':
+                usage();
+                break;
+            case 'z':
+                _config->Set("Inapt::AutomaticRemove", true);
+                break;
+            case 's':
+                _config->Set("Inapt::Simulate", true);
+                break;
+            case 'u':
+                _config->Set("Inapt::Purge", true);
+                break;
+            default:
+                fatal("error parsing arguments");
+        }
+    }
+
+    int num_files = argc - optind;
+
+    inapt_block context;
+    std::vector<inapt_package *> final_actions;
+
+    if (!num_files)
+        parser(NULL, &context);
+
+    while (num_files--)
+        parser(argv[optind++], &context);
+
+    auto_profiles(&defines);
+    eval_profiles(&context, &defines);
+    debug_profiles(&defines);
+    eval_block(&context, &defines, &final_actions);
+    exec_actions(&final_actions);
+
+    if (_error->PendingError()) {
+       _error->DumpErrors();
+        exit(1);
+    }
 }