X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/06d5520f0787f5fdb3fe0def435709054a26dbda..1b4560fec66a6e7b2dff9aaa19095fb8423f69a0:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index 8162405c7..14ce133cf 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -8,39 +8,81 @@ ##################################################################### */ /*}}}*/ // Includes /*{{{*/ +#include + #include #include #include #include +#include #include -#include #include +#include +#include +#include #include #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include +#include +#include +#include #include #include #include #include +#include -#include #include /*}}}*/ using namespace std; +class pkgDPkgPMPrivate +{ +public: + pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0), + term_out(NULL), history_out(NULL), + progress(NULL), master(-1), slave(-1) + { + dpkgbuf[0] = '\0'; + } + ~pkgDPkgPMPrivate() + { + } + bool stdin_is_dev_null; + // the buffer we use for the dpkg status-fd reading + char dpkgbuf[1024]; + int dpkgbuf_pos; + FILE *term_out; + FILE *history_out; + string dpkg_error; + APT::Progress::PackageManager *progress; + + // pty stuff + struct termios tt; + int master; + int slave; + + // signals + sigset_t sigmask; + sigset_t original_sigmask; + +}; + namespace { // Maps the dpkg "processing" info to human readable names. Entry 0 @@ -50,6 +92,7 @@ namespace std::make_pair("configure", N_("Configuring %s")), std::make_pair("remove", N_("Removing %s")), std::make_pair("purge", N_("Completely removing %s")), + std::make_pair("disappear", N_("Noting disappearance of %s")), std::make_pair("trigproc", N_("Running post-installation trigger %s")) }; @@ -85,7 +128,7 @@ ionice(int PID) { if (!FileExists("/usr/bin/ionice")) return false; - pid_t Process = ExecFork(); + pid_t Process = ExecFork(); if (Process == 0) { char buf[32]; @@ -100,13 +143,68 @@ ionice(int PID) return ExecWait(Process, "ionice"); } +static std::string getDpkgExecutable() +{ + string Tmp = _config->Find("Dir::Bin::dpkg","dpkg"); + string const dpkgChrootDir = _config->FindDir("DPkg::Chroot-Directory", "/"); + size_t dpkgChrootLen = dpkgChrootDir.length(); + if (dpkgChrootDir != "/" && Tmp.find(dpkgChrootDir) == 0) + { + if (dpkgChrootDir[dpkgChrootLen - 1] == '/') + --dpkgChrootLen; + Tmp = Tmp.substr(dpkgChrootLen); + } + return Tmp; +} + +// dpkgChrootDirectory - chrooting for dpkg if needed /*{{{*/ +static void dpkgChrootDirectory() +{ + std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory"); + if (chrootDir == "/") + return; + std::cerr << "Chrooting into " << chrootDir << std::endl; + if (chroot(chrootDir.c_str()) != 0) + _exit(100); + if (chdir("/") != 0) + _exit(100); +} + /*}}}*/ + + +// FindNowVersion - Helper to find a Version in "now" state /*{{{*/ +// --------------------------------------------------------------------- +/* This is helpful when a package is no longer installed but has residual + * config files + */ +static +pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg) +{ + pkgCache::VerIterator Ver; + for (Ver = Pkg.VersionList(); Ver.end() == false; ++Ver) + { + pkgCache::VerFileIterator Vf = Ver.FileList(); + pkgCache::PkgFileIterator F = Vf.File(); + for (F = Vf.File(); F.end() == false; ++F) + { + if (F && F.Archive()) + { + if (strcmp(F.Archive(), "now")) + return Ver; + } + } + } + return Ver; +} + /*}}}*/ + // DPkgPM::pkgDPkgPM - Constructor /*{{{*/ // --------------------------------------------------------------------- /* */ pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) - : pkgPackageManager(Cache), dpkgbuf_pos(0), - term_out(NULL), PackagesDone(0), PackagesTotal(0) + : pkgPackageManager(Cache), pkgFailures(0), PackagesDone(0), PackagesTotal(0) { + d = new pkgDPkgPMPrivate(); } /*}}}*/ // DPkgPM::pkgDPkgPM - Destructor /*{{{*/ @@ -114,6 +212,7 @@ pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) /* */ pkgDPkgPM::~pkgDPkgPM() { + delete d; } /*}}}*/ // DPkgPM::Install - Install a package /*{{{*/ @@ -122,9 +221,21 @@ pkgDPkgPM::~pkgDPkgPM() bool pkgDPkgPM::Install(PkgIterator Pkg,string File) { if (File.empty() == true || Pkg.end() == true) - return _error->Error("Internal Error, No file name for %s",Pkg.Name()); + return _error->Error("Internal Error, No file name for %s",Pkg.FullName().c_str()); + + // If the filename string begins with DPkg::Chroot-Directory, return the + // substr that is within the chroot so dpkg can access it. + string const chrootdir = _config->FindDir("DPkg::Chroot-Directory","/"); + if (chrootdir != "/" && File.find(chrootdir) == 0) + { + size_t len = chrootdir.length(); + if (chrootdir.at(len - 1) == '/') + len--; + List.push_back(Item(Item::Install,Pkg,File.substr(len))); + } + else + List.push_back(Item(Item::Install,Pkg,File)); - List.push_back(Item(Item::Install,Pkg,File)); return true; } /*}}}*/ @@ -161,15 +272,23 @@ bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge) return true; } /*}}}*/ -// DPkgPM::SendV2Pkgs - Send version 2 package info /*{{{*/ +// DPkgPM::SendPkgInfo - Send info for install-pkgs hook /*{{{*/ // --------------------------------------------------------------------- /* This is part of the helper script communication interface, it sends very complete information down to the other end of the pipe.*/ bool pkgDPkgPM::SendV2Pkgs(FILE *F) { - fprintf(F,"VERSION 2\n"); - - /* Write out all of the configuration directives by walking the + return SendPkgsInfo(F, 2); +} +bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version) +{ + // This version of APT supports only v3, so don't sent higher versions + if (Version <= 3) + fprintf(F,"VERSION %u\n", Version); + else + fprintf(F,"VERSION 3\n"); + + /* Write out all of the configuration directives by walking the configuration tree */ const Configuration::Item *Top = _config->Tree(0); for (; Top != 0;) @@ -195,7 +314,7 @@ bool pkgDPkgPM::SendV2Pkgs(FILE *F) fprintf(F,"\n"); // Write out the package actions in order. - for (vector::iterator I = List.begin(); I != List.end(); I++) + for (vector::iterator I = List.begin(); I != List.end(); ++I) { if(I->Pkg.end() == true) continue; @@ -203,30 +322,51 @@ bool pkgDPkgPM::SendV2Pkgs(FILE *F) pkgDepCache::StateCache &S = Cache[I->Pkg]; fprintf(F,"%s ",I->Pkg.Name()); - // Current version - if (I->Pkg->CurrentVer == 0) - fprintf(F,"- "); + + // Current version which we are going to replace + pkgCache::VerIterator CurVer = I->Pkg.CurrentVer(); + if (CurVer.end() == true && (I->Op == Item::Remove || I->Op == Item::Purge)) + CurVer = FindNowVersion(I->Pkg); + + if (CurVer.end() == true) + { + if (Version <= 2) + fprintf(F, "- "); + else + fprintf(F, "- - none "); + } else - fprintf(F,"%s ",I->Pkg.CurrentVer().VerStr()); - - // Show the compare operator - // Target version + { + fprintf(F, "%s ", CurVer.VerStr()); + if (Version >= 3) + fprintf(F, "%s %s ", CurVer.Arch(), CurVer.MultiArchType()); + } + + // Show the compare operator between current and install version if (S.InstallVer != 0) { + pkgCache::VerIterator const InstVer = S.InstVerIter(Cache); int Comp = 2; - if (I->Pkg->CurrentVer != 0) - Comp = S.InstVerIter(Cache).CompareVer(I->Pkg.CurrentVer()); + if (CurVer.end() == false) + Comp = InstVer.CompareVer(CurVer); if (Comp < 0) fprintf(F,"> "); - if (Comp == 0) + else if (Comp == 0) fprintf(F,"= "); - if (Comp > 0) + else if (Comp > 0) fprintf(F,"< "); - fprintf(F,"%s ",S.InstVerIter(Cache).VerStr()); + fprintf(F, "%s ", InstVer.VerStr()); + if (Version >= 3) + fprintf(F, "%s %s ", InstVer.Arch(), InstVer.MultiArchType()); } else - fprintf(F,"> - "); - + { + if (Version <= 2) + fprintf(F, "> - "); + else + fprintf(F, "> - - none "); + } + // Show the filename/operation if (I->Op == Item::Install) { @@ -236,9 +376,9 @@ bool pkgDPkgPM::SendV2Pkgs(FILE *F) else fprintf(F,"%s\n",I->File.c_str()); } - if (I->Op == Item::Configure) + else if (I->Op == Item::Configure) fprintf(F,"**CONFIGURE**\n"); - if (I->Op == Item::Remove || + else if (I->Op == Item::Remove || I->Op == Item::Purge) fprintf(F,"**REMOVE**\n"); @@ -274,24 +414,37 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos); unsigned int Version = _config->FindI(OptSec+"::Version",1); + unsigned int InfoFD = _config->FindI(OptSec + "::InfoFD", STDIN_FILENO); // Create the pipes + std::set KeepFDs; + MergeKeepFdsFromConfiguration(KeepFDs); int Pipes[2]; if (pipe(Pipes) != 0) return _error->Errno("pipe","Failed to create IPC pipe to subprocess"); - SetCloseExec(Pipes[0],true); + if (InfoFD != (unsigned)Pipes[0]) + SetCloseExec(Pipes[0],true); + else + KeepFDs.insert(Pipes[0]); + + SetCloseExec(Pipes[1],true); - + // Purified Fork for running the script - pid_t Process = ExecFork(); + pid_t Process = ExecFork(KeepFDs); if (Process == 0) { // Setup the FDs - dup2(Pipes[0],STDIN_FILENO); + dup2(Pipes[0], InfoFD); SetCloseExec(STDOUT_FILENO,false); - SetCloseExec(STDIN_FILENO,false); + SetCloseExec(STDIN_FILENO,false); SetCloseExec(STDERR_FILENO,false); + string hookfd; + strprintf(hookfd, "%d", InfoFD); + setenv("APT_HOOK_INFO_FD", hookfd.c_str(), 1); + + dpkgChrootDirectory(); const char *Args[4]; Args[0] = "/bin/sh"; Args[1] = "-c"; @@ -306,10 +459,9 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) return _error->Errno("fdopen","Faild to open new FD"); // Feed it the filenames. - bool Die = false; if (Version <= 1) { - for (vector::iterator I = List.begin(); I != List.end(); I++) + for (vector::iterator I = List.begin(); I != List.end(); ++I) { // Only deal with packages to be installed from .deb if (I->Op != Item::Install) @@ -323,14 +475,11 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) into the pipe. */ fprintf(F,"%s\n",I->File.c_str()); if (ferror(F) != 0) - { - Die = true; break; - } } } else - Die = !SendV2Pkgs(F); + SendPkgsInfo(F, Version); fclose(F); @@ -341,7 +490,6 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) return true; } - /*}}}*/ // DPkgPM::DoStdin - Read stdin and pass to slave pty /*{{{*/ // --------------------------------------------------------------------- @@ -352,9 +500,9 @@ void pkgDPkgPM::DoStdin(int master) unsigned char input_buf[256] = {0,}; ssize_t len = read(0, input_buf, sizeof(input_buf)); if (len) - write(master, input_buf, len); + FileFd::Write(master, input_buf, len); else - stdin_is_dev_null = true; + d->stdin_is_dev_null = true; } /*}}}*/ // DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/ @@ -371,174 +519,265 @@ void pkgDPkgPM::DoTerminalPty(int master) { // this happens when the child is about to exit, we // give it time to actually exit, otherwise we run - // into a race - usleep(500000); + // into a race so we sleep for half a second. + struct timespec sleepfor = { 0, 500000000 }; + nanosleep(&sleepfor, NULL); return; } if(len <= 0) return; - write(1, term_buf, len); - if(term_out) - fwrite(term_buf, len, sizeof(char), term_out); + FileFd::Write(1, term_buf, len); + if(d->term_out) + fwrite(term_buf, len, sizeof(char), d->term_out); } /*}}}*/ // DPkgPM::ProcessDpkgStatusBuf /*{{{*/ // --------------------------------------------------------------------- /* */ -void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, char *line) +void pkgDPkgPM::ProcessDpkgStatusLine(char *line) { bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false); - // the status we output - ostringstream status; - if (Debug == true) std::clog << "got from dpkg '" << line << "'" << std::endl; - /* dpkg sends strings like this: - 'status: : ' - errors look like this: - 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data - and conffile-prompt like this - 'status: conffile-prompt: conffile : 'current-conffile' 'new-conffile' useredited distedited + 'status: : ' + 'status: :: ' - Newer versions of dpkg sent also: - 'processing: install: pkg' - 'processing: configure: pkg' - 'processing: remove: pkg' - 'processing: purge: pkg' - but for apt is it a ignored "unknown" action - 'processing: trigproc: trigger' - + 'processing: {install,configure,remove,purge,disappear,trigproc}: pkg' + 'processing: {install,configure,remove,purge,disappear,trigproc}: trigger' */ - char* list[5]; - // dpkg sends multiline error messages sometimes (see - // #374195 for a example. we should support this by - // either patching dpkg to not send multiline over the - // statusfd or by rewriting the code here to deal with - // it. for now we just ignore it and not crash - TokSplitString(':', line, list, sizeof(list)/sizeof(list[0])); - if( list[0] == NULL || list[1] == NULL || list[2] == NULL) + + // we need to split on ": " (note the appended space) as the ':' is + // part of the pkgname:arch information that dpkg sends + // + // A dpkg error message may contain additional ":" (like + // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..." + // so we need to ensure to not split too much + std::vector list = StringSplit(line, ": ", 4); + if(list.size() < 3) { if (Debug == true) std::clog << "ignoring line: not enough ':'" << std::endl; return; } - const char* const pkg = list[1]; - const char* action = _strstrip(list[2]); + + // build the (prefix, pkgname, action) tuple, position of this + // is different for "processing" or "status" messages + std::string prefix = APT::String::Strip(list[0]); + std::string pkgname; + std::string action; + ostringstream status; + + // "processing" has the form "processing: action: pkg or trigger" + // with action = ["install", "configure", "remove", "purge", "disappear", + // "trigproc"] + if (prefix == "processing") + { + pkgname = APT::String::Strip(list[2]); + action = APT::String::Strip(list[1]); + } + // "status" has the form: "status: pkg: state" + // with state in ["half-installed", "unpacked", "half-configured", + // "installed", "config-files", "not-installed"] + else if (prefix == "status") + { + pkgname = APT::String::Strip(list[1]); + action = APT::String::Strip(list[2]); + } else { + if (Debug == true) + std::clog << "unknown prefix '" << prefix << "'" << std::endl; + return; + } + + + /* handle the special cases first: + + errors look like this: + 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data + and conffile-prompt like this + 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited + */ + if (prefix == "status") + { + if(action == "error") + { + d->progress->Error(list[1], PackagesDone, PackagesTotal, + list[3]); + pkgFailures++; + WriteApportReport(list[1].c_str(), list[3].c_str()); + return; + } + else if(action == "conffile-prompt") + { + d->progress->ConffilePrompt(list[1], PackagesDone, PackagesTotal, + list[3]); + return; + } + } + + // at this point we know that we should have a valid pkgname, so build all + // the info from it + + // dpkg does not send always send "pkgname:arch" so we add it here + // if needed + if (pkgname.find(":") == std::string::npos) + { + // find the package in the group that is in a touched by dpkg + // if there are multiple dpkg will send us a full pkgname:arch + pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname); + if (Grp.end() == false) + { + pkgCache::PkgIterator P = Grp.PackageList(); + for (; P.end() != true; P = Grp.NextPkg(P)) + { + if(Cache[P].Mode != pkgDepCache::ModeKeep) + { + pkgname = P.FullName(); + break; + } + } + } + } + + const char* const pkg = pkgname.c_str(); + std::string short_pkgname = StringSplit(pkgname, ":")[0]; + std::string arch = ""; + if (pkgname.find(":") != string::npos) + arch = StringSplit(pkgname, ":")[1]; + std::string i18n_pkgname = pkgname; + if (arch.size() != 0) + strprintf(i18n_pkgname, "%s (%s)", short_pkgname.c_str(), arch.c_str()); // 'processing' from dpkg looks like // 'processing: action: pkg' - if(strncmp(list[0], "processing", strlen("processing")) == 0) + if(prefix == "processing") { - char s[200]; - const char* const pkg_or_trigger = _strstrip(list[2]); - action = _strstrip( list[1]); const std::pair * const iter = std::find_if(PackageProcessingOpsBegin, PackageProcessingOpsEnd, - MatchProcessingOp(action)); + MatchProcessingOp(action.c_str())); if(iter == PackageProcessingOpsEnd) { if (Debug == true) std::clog << "ignoring unknown action: " << action << std::endl; return; } - snprintf(s, sizeof(s), _(iter->second), pkg_or_trigger); - - status << "pmstatus:" << pkg_or_trigger - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << s - << endl; - if(OutStatusFd > 0) - write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; + std::string msg; + strprintf(msg, _(iter->second), i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); + + // FIXME: this needs a muliarch testcase + // FIXME2: is "pkgname" here reliable with dpkg only sending us + // short pkgnames? + if (action == "disappear") + handleDisappearAction(pkgname); return; - } + } - if(strncmp(action,"error",strlen("error")) == 0) + if (prefix == "status") { - status << "pmerror:" << list[1] - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << list[3] - << endl; - if(OutStatusFd > 0) - write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; - return; + vector const &states = PackageOps[pkg]; + const char *next_action = NULL; + if(PackageOpsDone[pkg] < states.size()) + next_action = states[PackageOpsDone[pkg]].state; + // check if the package moved to the next dpkg state + if(next_action && (action == next_action)) + { + // only read the translation if there is actually a next + // action + const char *translation = _(states[PackageOpsDone[pkg]].str); + std::string msg; + + // we moved from one dpkg state to a new one, report that + PackageOpsDone[pkg]++; + PackagesDone++; + + strprintf(msg, translation, i18n_pkgname.c_str()); + d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); + + } + if (Debug == true) + std::clog << "(parsed from dpkg) pkg: " << short_pkgname + << " action: " << action << endl; } - else if(strncmp(action,"conffile",strlen("conffile")) == 0) - { - status << "pmconffile:" << list[1] - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << list[3] - << endl; - if(OutStatusFd > 0) - write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; +} + /*}}}*/ +// DPkgPM::handleDisappearAction /*{{{*/ +void pkgDPkgPM::handleDisappearAction(string const &pkgname) +{ + pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); + if (unlikely(Pkg.end() == true)) return; - } - vector const &states = PackageOps[pkg]; - const char *next_action = NULL; - if(PackageOpsDone[pkg] < states.size()) - next_action = states[PackageOpsDone[pkg]].state; - // check if the package moved to the next dpkg state - if(next_action && (strcmp(action, next_action) == 0)) + // record the package name for display and stuff later + disappearedPkgs.insert(Pkg.FullName(true)); + + // the disappeared package was auto-installed - nothing to do + if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + return; + pkgCache::VerIterator PkgVer = Cache[Pkg].InstVerIter(Cache); + if (unlikely(PkgVer.end() == true)) + return; + /* search in the list of dependencies for (Pre)Depends, + check if this dependency has a Replaces on our package + and if so transfer the manual installed flag to it */ + for (pkgCache::DepIterator Dep = PkgVer.DependsList(); Dep.end() != true; ++Dep) { - // only read the translation if there is actually a next - // action - const char *translation = _(states[PackageOpsDone[pkg]].str); - char s[200]; - snprintf(s, sizeof(s), translation, pkg); - - // we moved from one dpkg state to a new one, report that - PackageOpsDone[pkg]++; - PackagesDone++; - // build the status str - status << "pmstatus:" << pkg - << ":" << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << s - << endl; - if(OutStatusFd > 0) - write(OutStatusFd, status.str().c_str(), status.str().size()); - if (Debug == true) - std::clog << "send: '" << status.str() << "'" << endl; + if (Dep->Type != pkgCache::Dep::Depends && + Dep->Type != pkgCache::Dep::PreDepends) + continue; + pkgCache::PkgIterator Tar = Dep.TargetPkg(); + if (unlikely(Tar.end() == true)) + continue; + // the package is already marked as manual + if ((Cache[Tar].Flags & pkgCache::Flag::Auto) != pkgCache::Flag::Auto) + continue; + pkgCache::VerIterator TarVer = Cache[Tar].InstVerIter(Cache); + if (TarVer.end() == true) + continue; + for (pkgCache::DepIterator Rep = TarVer.DependsList(); Rep.end() != true; ++Rep) + { + if (Rep->Type != pkgCache::Dep::Replaces) + continue; + if (Pkg != Rep.TargetPkg()) + continue; + // okay, they are strongly connected - transfer manual-bit + if (Debug == true) + std::clog << "transfer manual-bit from disappeared »" << pkgname << "« to »" << Tar.FullName() << "«" << std::endl; + Cache[Tar].Flags &= ~Flag::Auto; + break; + } } - if (Debug == true) - std::clog << "(parsed from dpkg) pkg: " << pkg - << " action: " << action << endl; } /*}}}*/ // DPkgPM::DoDpkgStatusFd /*{{{*/ // --------------------------------------------------------------------- /* */ -void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) +void pkgDPkgPM::DoDpkgStatusFd(int statusfd) { char *p, *q; int len; - len=read(statusfd, &dpkgbuf[dpkgbuf_pos], sizeof(dpkgbuf)-dpkgbuf_pos); - dpkgbuf_pos += len; + len=read(statusfd, &d->dpkgbuf[d->dpkgbuf_pos], sizeof(d->dpkgbuf)-d->dpkgbuf_pos); + d->dpkgbuf_pos += len; if(len <= 0) return; // process line by line if we have a buffer - p = q = dpkgbuf; - while((q=(char*)memchr(p, '\n', dpkgbuf+dpkgbuf_pos-p)) != NULL) + p = q = d->dpkgbuf; + while((q=(char*)memchr(p, '\n', d->dpkgbuf+d->dpkgbuf_pos-p)) != NULL) { *q = 0; - ProcessDpkgStatusLine(OutStatusFd, p); + ProcessDpkgStatusLine(p); p=q+1; // continue with next line } // now move the unprocessed bits (after the final \n that is now a 0x0) - // to the start and update dpkgbuf_pos - p = (char*)memrchr(dpkgbuf, 0, dpkgbuf_pos); + // to the start and update d->dpkgbuf_pos + p = (char*)memrchr(d->dpkgbuf, 0, d->dpkgbuf_pos); if(p == NULL) return; @@ -546,71 +785,109 @@ void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) p++; // move the unprocessed tail to the start and update pos - memmove(dpkgbuf, p, p-dpkgbuf); - dpkgbuf_pos = dpkgbuf+dpkgbuf_pos-p; + memmove(d->dpkgbuf, p, p-d->dpkgbuf); + d->dpkgbuf_pos = d->dpkgbuf+d->dpkgbuf_pos-p; } /*}}}*/ +// DPkgPM::WriteHistoryTag /*{{{*/ +void pkgDPkgPM::WriteHistoryTag(string const &tag, string value) +{ + size_t const length = value.length(); + if (length == 0) + return; + // poor mans rstrip(", ") + if (value[length-2] == ',' && value[length-1] == ' ') + value.erase(length - 2, 2); + fprintf(d->history_out, "%s: %s\n", tag.c_str(), value.c_str()); +} /*}}}*/ // DPkgPM::OpenLog /*{{{*/ bool pkgDPkgPM::OpenLog() { - string logdir = _config->FindDir("Dir::Log"); - if(not FileExists(logdir)) + string const logdir = _config->FindDir("Dir::Log"); + if(CreateAPTDirectoryIfNeeded(logdir, logdir) == false) + // FIXME: use a better string after freeze return _error->Error(_("Directory '%s' missing"), logdir.c_str()); // get current time char timestr[200]; - time_t t = time(NULL); - struct tm *tmp = localtime(&t); + time_t const t = time(NULL); + struct tm const * const tmp = localtime(&t); strftime(timestr, sizeof(timestr), "%F %T", tmp); // open terminal log - string logfile_name = flCombine(logdir, + string const logfile_name = flCombine(logdir, _config->Find("Dir::Log::Terminal")); if (!logfile_name.empty()) { - term_out = fopen(logfile_name.c_str(),"a"); - chmod(logfile_name.c_str(), 0600); - fprintf(term_out, "\n\nLog started: "); - fprintf(term_out, "%s", timestr); - fprintf(term_out, "\n"); + d->term_out = fopen(logfile_name.c_str(),"a"); + if (d->term_out == NULL) + return _error->WarningE("OpenLog", _("Could not open file '%s'"), logfile_name.c_str()); + setvbuf(d->term_out, NULL, _IONBF, 0); + SetCloseExec(fileno(d->term_out), true); + if (getuid() == 0) // if we aren't root, we can't chown a file, so don't try it + { + struct passwd *pw = getpwnam("root"); + struct group *gr = getgrnam("adm"); + if (pw != NULL && gr != NULL && chown(logfile_name.c_str(), pw->pw_uid, gr->gr_gid) != 0) + _error->WarningE("OpenLog", "chown to root:adm of file %s failed", logfile_name.c_str()); + } + if (chmod(logfile_name.c_str(), 0640) != 0) + _error->WarningE("OpenLog", "chmod 0640 of file %s failed", logfile_name.c_str()); + fprintf(d->term_out, "\nLog started: %s\n", timestr); } - // write - string history_name = flCombine(logdir, + // write your history + string const history_name = flCombine(logdir, _config->Find("Dir::Log::History")); if (!history_name.empty()) { - history_out = fopen(history_name.c_str(),"a"); + d->history_out = fopen(history_name.c_str(),"a"); + if (d->history_out == NULL) + return _error->WarningE("OpenLog", _("Could not open file '%s'"), history_name.c_str()); + SetCloseExec(fileno(d->history_out), true); chmod(history_name.c_str(), 0644); - fprintf(history_out, "\nStart-Date: %s\n", timestr); - string remove, purge, install, upgrade, downgrade; - for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; I++) + fprintf(d->history_out, "\nStart-Date: %s\n", timestr); + string remove, purge, install, reinstall, upgrade, downgrade; + for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; ++I) { - if (Cache[I].Upgrade()) - upgrade += I.Name() + string(" (") + Cache[I].CurVersion + string(", ") + Cache[I].CandVersion + string(") "); - else if (Cache[I].Downgrade()) - downgrade += I.Name() + string(" (") + Cache[I].CurVersion + string(", ") + Cache[I].CandVersion + string(") "); - else if (Cache[I].Install()) - install += I.Name() + string(" (") + Cache[I].CandVersion + string(") "); - else if (Cache[I].Delete()) - { - if ((Cache[I].iFlags & pkgDepCache::Purge) == pkgDepCache::Purge) - purge += I.Name() + string(" (") + Cache[I].CurVersion + string(") "); - else - remove += I.Name() + string(" (") + Cache[I].CurVersion + string(") "); + enum { CANDIDATE, CANDIDATE_AUTO, CURRENT_CANDIDATE, CURRENT } infostring; + string *line = NULL; + #define HISTORYINFO(X, Y) { line = &X; infostring = Y; } + if (Cache[I].NewInstall() == true) + HISTORYINFO(install, CANDIDATE_AUTO) + else if (Cache[I].ReInstall() == true) + HISTORYINFO(reinstall, CANDIDATE) + else if (Cache[I].Upgrade() == true) + HISTORYINFO(upgrade, CURRENT_CANDIDATE) + else if (Cache[I].Downgrade() == true) + HISTORYINFO(downgrade, CURRENT_CANDIDATE) + else if (Cache[I].Delete() == true) + HISTORYINFO((Cache[I].Purge() ? purge : remove), CURRENT) + else + continue; + #undef HISTORYINFO + line->append(I.FullName(false)).append(" ("); + switch (infostring) { + case CANDIDATE: line->append(Cache[I].CandVersion); break; + case CANDIDATE_AUTO: + line->append(Cache[I].CandVersion); + if ((Cache[I].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) + line->append(", automatic"); + break; + case CURRENT_CANDIDATE: line->append(Cache[I].CurVersion).append(", ").append(Cache[I].CandVersion); break; + case CURRENT: line->append(Cache[I].CurVersion); break; } + line->append("), "); } - if (install.size() > 0) - fprintf(history_out, "Install: %s\n", install.c_str()); - if (upgrade.size() > 0) - fprintf(history_out, "Upgrade: %s\n", upgrade.c_str()); - if (downgrade.size() > 0) - fprintf(history_out, "Downgrade: %s\n", downgrade.c_str()); - if (remove.size() > 0) - fprintf(history_out, "Remove: %s\n", remove.c_str()); - if (purge.size() > 0) - fprintf(history_out, "Purge: %s\n", purge.c_str()); - fflush(history_out); + if (_config->Exists("Commandline::AsString") == true) + WriteHistoryTag("Commandline", _config->Find("Commandline::AsString")); + WriteHistoryTag("Install", install); + WriteHistoryTag("Reinstall", reinstall); + WriteHistoryTag("Upgrade", upgrade); + WriteHistoryTag("Downgrade",downgrade); + WriteHistoryTag("Remove",remove); + WriteHistoryTag("Purge",purge); + fflush(d->history_out); } return true; @@ -624,26 +901,43 @@ bool pkgDPkgPM::CloseLog() struct tm *tmp = localtime(&t); strftime(timestr, sizeof(timestr), "%F %T", tmp); - if(term_out) + if(d->term_out) { - fprintf(term_out, "Log ended: "); - fprintf(term_out, "%s", timestr); - fprintf(term_out, "\n"); - fclose(term_out); + fprintf(d->term_out, "Log ended: "); + fprintf(d->term_out, "%s", timestr); + fprintf(d->term_out, "\n"); + fclose(d->term_out); } - term_out = NULL; + d->term_out = NULL; - if(history_out) + if(d->history_out) { - if (dpkg_error.size() > 0) - fprintf(history_out, "Error: %s\n", dpkg_error.c_str()); - fprintf(history_out, "End-Date: %s\n", timestr); - fclose(history_out); + if (disappearedPkgs.empty() == false) + { + string disappear; + for (std::set::const_iterator d = disappearedPkgs.begin(); + d != disappearedPkgs.end(); ++d) + { + pkgCache::PkgIterator P = Cache.FindPkg(*d); + disappear.append(*d); + if (P.end() == true) + disappear.append(", "); + else + disappear.append(" (").append(Cache[P].CurVersion).append("), "); + } + WriteHistoryTag("Disappeared", disappear); + } + if (d->dpkg_error.empty() == false) + fprintf(d->history_out, "Error: %s\n", d->dpkg_error.c_str()); + fprintf(d->history_out, "End-Date: %s\n", timestr); + fclose(d->history_out); } + d->history_out = NULL; return true; } /*}}}*/ + /*}}}*/ /*{{{*/ // This implements a racy version of pselect for those architectures // that don't have a working implementation. @@ -664,40 +958,11 @@ static int racy_pselect(int nfds, fd_set *readfds, fd_set *writefds, sigprocmask(SIG_SETMASK, &origmask, 0); return retval; } -/*}}}*/ -// DPkgPM::Go - Run the sequence /*{{{*/ -// --------------------------------------------------------------------- -/* This globs the operations and calls dpkg - * - * If it is called with "OutStatusFd" set to a valid file descriptor - * apt will report the install progress over this fd. It maps the - * dpkg states a package goes through to human readable (and i10n-able) - * names and calculates a percentage for each step. -*/ -bool pkgDPkgPM::Go(int OutStatusFd) -{ - fd_set rfds; - struct timespec tv; - sigset_t sigmask; - sigset_t original_sigmask; - - unsigned int const MaxArgs = _config->FindI("Dpkg::MaxArgs",8*1024); - unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",32*1024); - bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false); - - if (RunScripts("DPkg::Pre-Invoke") == false) - return false; - - if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false) - return false; - - // support subpressing of triggers processing for special - // cases like d-i that runs the triggers handling manually - bool const SmartConf = (_config->Find("PackageManager::Configure", "all") != "all"); - bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false); - if (_config->FindB("DPkg::ConfigurePending", SmartConf) == true) - List.push_back(Item(Item::ConfigurePending, PkgIterator())); + /*}}}*/ +// DPkgPM::BuildPackagesProgressMap /*{{{*/ +void pkgDPkgPM::BuildPackagesProgressMap() +{ // map the dpkg states to the operations that are performed // (this is sorted in the same way as Item::Ops) static const struct DpkgState DpkgStatesOpMap[][7] = { @@ -733,32 +998,204 @@ bool pkgDPkgPM::Go(int OutStatusFd) // that will be [installed|configured|removed|purged] and add // them to the PackageOps map (the dpkg states it goes through) // and the PackageOpsTranslations (human readable strings) - for (vector::const_iterator I = List.begin(); I != List.end();I++) + for (vector::const_iterator I = List.begin(); I != List.end(); ++I) { if((*I).Pkg.end() == true) continue; - string const name = (*I).Pkg.Name(); + string const name = (*I).Pkg.FullName(); PackageOpsDone[name] = 0; - for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; i++) + for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i) { PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]); PackagesTotal++; } } +} + /*}}}*/ +#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13) +bool pkgDPkgPM::Go(int StatusFd) +{ + APT::Progress::PackageManager *progress = NULL; + if (StatusFd == -1) + progress = APT::Progress::PackageManagerProgressFactory(); + else + progress = new APT::Progress::PackageManagerProgressFd(StatusFd); + + return GoNoABIBreak(progress); +} +#endif + +void pkgDPkgPM::StartPtyMagic() +{ + // setup the pty and stuff + struct winsize win; + + // if tcgetattr does not return zero there was a error + // and we do not do any pty magic + _error->PushToStack(); + if (tcgetattr(STDOUT_FILENO, &d->tt) == 0) + { + ioctl(1, TIOCGWINSZ, (char *)&win); + if (_config->FindB("Dpkg::Use-Pty", true) == false) + { + d->master = d->slave = -1; + } else if (openpty(&d->master, &d->slave, NULL, &d->tt, &win) < 0) + { + _error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); + d->master = d->slave = -1; + } else { + struct termios rtt; + rtt = d->tt; + cfmakeraw(&rtt); + rtt.c_lflag &= ~ECHO; + rtt.c_lflag |= ISIG; + // block SIGTTOU during tcsetattr to prevent a hang if + // the process is a member of the background process group + // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html + sigemptyset(&d->sigmask); + sigaddset(&d->sigmask, SIGTTOU); + sigprocmask(SIG_BLOCK,&d->sigmask, &d->original_sigmask); + tcsetattr(0, TCSAFLUSH, &rtt); + sigprocmask(SIG_SETMASK, &d->original_sigmask, 0); + } + } + // complain only if stdout is either a terminal (but still failed) or is an invalid + // descriptor otherwise we would complain about redirection to e.g. /dev/null as well. + else if (isatty(STDOUT_FILENO) == 1 || errno == EBADF) + _error->Errno("tcgetattr", _("Can not write log (%s)"), _("Is stdout a terminal?")); + + if (_error->PendingError() == true) + _error->DumpErrors(std::cerr); + _error->RevertToStack(); +} + +void pkgDPkgPM::StopPtyMagic() +{ + if(d->slave > 0) + close(d->slave); + if(d->master >= 0) + { + tcsetattr(0, TCSAFLUSH, &d->tt); + close(d->master); + } +} + +// DPkgPM::Go - Run the sequence /*{{{*/ +// --------------------------------------------------------------------- +/* This globs the operations and calls dpkg + * + * If it is called with a progress object apt will report the install + * progress to this object. It maps the dpkg states a package goes + * through to human readable (and i10n-able) + * names and calculates a percentage for each step. + */ +#if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR >= 13) +bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) +#else +bool pkgDPkgPM::GoNoABIBreak(APT::Progress::PackageManager *progress) +#endif +{ + pkgPackageManager::SigINTStop = false; + d->progress = progress; + + // Generate the base argument list for dpkg + unsigned long StartSize = 0; + std::vector Args; + std::string DpkgExecutable = getDpkgExecutable(); + Args.push_back(DpkgExecutable.c_str()); + StartSize += DpkgExecutable.length(); + + // Stick in any custom dpkg options + Configuration::Item const *Opts = _config->Tree("DPkg::Options"); + if (Opts != 0) + { + Opts = Opts->Child; + for (; Opts != 0; Opts = Opts->Next) + { + if (Opts->Value.empty() == true) + continue; + Args.push_back(Opts->Value.c_str()); + StartSize += Opts->Value.length(); + } + } - stdin_is_dev_null = false; + size_t const BaseArgs = Args.size(); + // we need to detect if we can qualify packages with the architecture or not + Args.push_back("--assert-multi-arch"); + Args.push_back(NULL); + + pid_t dpkgAssertMultiArch = ExecFork(); + if (dpkgAssertMultiArch == 0) + { + dpkgChrootDirectory(); + // redirect everything to the ultimate sink as we only need the exit-status + int const nullfd = open("/dev/null", O_RDONLY); + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + execvp(Args[0], (char**) &Args[0]); + _error->WarningE("dpkgGo", "Can't detect if dpkg supports multi-arch!"); + _exit(2); + } + + fd_set rfds; + struct timespec tv; + + unsigned int const MaxArgs = _config->FindI("Dpkg::MaxArgs",8*1024); + unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",32*1024); + bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false); + + if (RunScripts("DPkg::Pre-Invoke") == false) + return false; + + if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false) + return false; + + // support subpressing of triggers processing for special + // cases like d-i that runs the triggers handling manually + bool const SmartConf = (_config->Find("PackageManager::Configure", "all") != "all"); + bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false); + if (_config->FindB("DPkg::ConfigurePending", SmartConf) == true) + List.push_back(Item(Item::ConfigurePending, PkgIterator())); + + // for the progress + BuildPackagesProgressMap(); + + d->stdin_is_dev_null = false; // create log OpenLog(); - // this loop is runs once per operation - for (vector::const_iterator I = List.begin(); I != List.end();) + bool dpkgMultiArch = false; + if (dpkgAssertMultiArch > 0) + { + int Status = 0; + while (waitpid(dpkgAssertMultiArch, &Status, 0) != dpkgAssertMultiArch) + { + if (errno == EINTR) + continue; + _error->WarningE("dpkgGo", _("Waited for %s but it wasn't there"), "dpkg --assert-multi-arch"); + break; + } + if (WIFEXITED(Status) == true && WEXITSTATUS(Status) == 0) + dpkgMultiArch = true; + } + + // start pty magic before the loop + StartPtyMagic(); + + // Tell the progress that its starting and fork dpkg + d->progress->Start(d->master); + + // this loop is runs once per dpkg operation + vector::const_iterator I = List.begin(); + while (I != List.end()) { // Do all actions with the same Op in one run vector::const_iterator J = I; if (TriggersPending == true) - for (; J != List.end(); J++) + for (; J != List.end(); ++J) { if (J->Op == I->Op) continue; @@ -770,138 +1207,161 @@ bool pkgDPkgPM::Go(int OutStatusFd) break; } else - for (; J != List.end() && J->Op == I->Op; J++) + for (; J != List.end() && J->Op == I->Op; ++J) /* nothing */; - // Generate the argument list - const char *Args[MaxArgs + 50]; - + // keep track of allocated strings for multiarch package names + std::vector Packages; + + // start with the baseset of arguments + unsigned long Size = StartSize; + Args.erase(Args.begin() + BaseArgs, Args.end()); + // Now check if we are within the MaxArgs limit // // this code below is problematic, because it may happen that // the argument list is split in a way that A depends on B // and they are in the same "--configure A B" run // - with the split they may now be configured in different - // runs + // runs, using Immediate-Configure-All can help prevent this. if (J - I > (signed)MaxArgs) + { J = I + MaxArgs; - - unsigned int n = 0; - unsigned long Size = 0; - string const Tmp = _config->Find("Dir::Bin::dpkg","dpkg"); - Args[n++] = Tmp.c_str(); - Size += strlen(Args[n-1]); - - // Stick in any custom dpkg options - Configuration::Item const *Opts = _config->Tree("DPkg::Options"); - if (Opts != 0) + unsigned long const size = MaxArgs + 10; + Args.reserve(size); + Packages.reserve(size); + } + else { - Opts = Opts->Child; - for (; Opts != 0; Opts = Opts->Next) - { - if (Opts->Value.empty() == true) - continue; - Args[n++] = Opts->Value.c_str(); - Size += Opts->Value.length(); - } + unsigned long const size = (J - I) + 10; + Args.reserve(size); + Packages.reserve(size); } - - char status_fd_buf[20]; + int fd[2]; - pipe(fd); - - Args[n++] = "--status-fd"; - Size += strlen(Args[n-1]); + if (pipe(fd) != 0) + return _error->Errno("pipe","Failed to create IPC pipe to dpkg"); + +#define ADDARG(X) Args.push_back(X); Size += strlen(X) +#define ADDARGC(X) Args.push_back(X); Size += sizeof(X) - 1 + + ADDARGC("--status-fd"); + char status_fd_buf[20]; snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]); - Args[n++] = status_fd_buf; - Size += strlen(Args[n-1]); + ADDARG(status_fd_buf); + unsigned long const Op = I->Op; switch (I->Op) { case Item::Remove: - Args[n++] = "--force-depends"; - Size += strlen(Args[n-1]); - Args[n++] = "--force-remove-essential"; - Size += strlen(Args[n-1]); - Args[n++] = "--remove"; - Size += strlen(Args[n-1]); + ADDARGC("--force-depends"); + ADDARGC("--force-remove-essential"); + ADDARGC("--remove"); break; case Item::Purge: - Args[n++] = "--force-depends"; - Size += strlen(Args[n-1]); - Args[n++] = "--force-remove-essential"; - Size += strlen(Args[n-1]); - Args[n++] = "--purge"; - Size += strlen(Args[n-1]); + ADDARGC("--force-depends"); + ADDARGC("--force-remove-essential"); + ADDARGC("--purge"); break; case Item::Configure: - Args[n++] = "--configure"; - Size += strlen(Args[n-1]); + ADDARGC("--configure"); break; case Item::ConfigurePending: - Args[n++] = "--configure"; - Size += strlen(Args[n-1]); - Args[n++] = "--pending"; - Size += strlen(Args[n-1]); + ADDARGC("--configure"); + ADDARGC("--pending"); break; case Item::TriggersPending: - Args[n++] = "--triggers-only"; - Size += strlen(Args[n-1]); - Args[n++] = "--pending"; - Size += strlen(Args[n-1]); + ADDARGC("--triggers-only"); + ADDARGC("--pending"); break; case Item::Install: - Args[n++] = "--unpack"; - Size += strlen(Args[n-1]); - Args[n++] = "--auto-deconfigure"; - Size += strlen(Args[n-1]); + ADDARGC("--unpack"); + ADDARGC("--auto-deconfigure"); break; } if (NoTriggers == true && I->Op != Item::TriggersPending && I->Op != Item::ConfigurePending) { - Args[n++] = "--no-triggers"; - Size += strlen(Args[n-1]); + ADDARGC("--no-triggers"); } +#undef ADDARGC // Write in the file or package names if (I->Op == Item::Install) { - for (;I != J && Size < MaxArgBytes; I++) + for (;I != J && Size < MaxArgBytes; ++I) { if (I->File[0] != '/') return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str()); - Args[n++] = I->File.c_str(); - Size += strlen(Args[n-1]); + Args.push_back(I->File.c_str()); + Size += I->File.length(); } - } + } else { - for (;I != J && Size < MaxArgBytes; I++) + string const nativeArch = _config->Find("APT::Architecture"); + unsigned long const oldSize = I->Op == Item::Configure ? Size : 0; + for (;I != J && Size < MaxArgBytes; ++I) { if((*I).Pkg.end() == true) continue; - Args[n++] = I->Pkg.Name(); - Size += strlen(Args[n-1]); - } - } - Args[n] = 0; + if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.FullName(true)) != disappearedPkgs.end()) + continue; + // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian + if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch || + strcmp(I->Pkg.Arch(), "all") == 0 || + strcmp(I->Pkg.Arch(), "none") == 0)) + { + char const * const name = I->Pkg.Name(); + ADDARG(name); + } + else + { + pkgCache::VerIterator PkgVer; + std::string name = I->Pkg.Name(); + if (Op == Item::Remove || Op == Item::Purge) + { + PkgVer = I->Pkg.CurrentVer(); + if(PkgVer.end() == true) + PkgVer = FindNowVersion(I->Pkg); + } + else + PkgVer = Cache[I->Pkg].InstVerIter(Cache); + if (strcmp(I->Pkg.Arch(), "none") == 0) + ; // never arch-qualify a package without an arch + else if (PkgVer.end() == false) + name.append(":").append(PkgVer.Arch()); + else + _error->Warning("Can not find PkgVer for '%s'", name.c_str()); + char * const fullname = strdup(name.c_str()); + Packages.push_back(fullname); + ADDARG(fullname); + } + } + // skip configure action if all sheduled packages disappeared + if (oldSize == Size) + continue; + } +#undef ADDARG + J = I; if (_config->FindB("Debug::pkgDPkgPM",false) == true) { - for (unsigned int k = 0; k != n; k++) - clog << Args[k] << ' '; + for (std::vector::const_iterator a = Args.begin(); + a != Args.end(); ++a) + clog << *a << ' '; clog << endl; continue; } - + Args.push_back(NULL); + cout << flush; clog << flush; cerr << flush; @@ -911,85 +1371,42 @@ bool pkgDPkgPM::Go(int OutStatusFd) it to all processes in the group. Since dpkg ignores the signal it doesn't die but we do! So we must also ignore it */ sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN); - sighandler_t old_SIGINT = signal(SIGINT,SIG_IGN); - + sighandler_t old_SIGINT = signal(SIGINT,SigINT); + + // Check here for any SIGINT + if (pkgPackageManager::SigINTStop && (Op == Item::Remove || Op == Item::Purge || Op == Item::Install)) + break; + + // ignore SIGHUP as well (debian #463030) sighandler_t old_SIGHUP = signal(SIGHUP,SIG_IGN); - struct termios tt; - struct winsize win; - int master = -1; - int slave = -1; - - // if tcgetattr does not return zero there was a error - // and we do not do any pty magic - if (tcgetattr(0, &tt) == 0) - { - ioctl(0, TIOCGWINSZ, (char *)&win); - if (openpty(&master, &slave, NULL, &tt, &win) < 0) - { - const char *s = _("Can not write log, openpty() " - "failed (/dev/pts not mounted?)\n"); - fprintf(stderr, "%s",s); - fprintf(term_out, "%s",s); - master = slave = -1; - } else { - struct termios rtt; - rtt = tt; - cfmakeraw(&rtt); - rtt.c_lflag &= ~ECHO; - // block SIGTTOU during tcsetattr to prevent a hang if - // the process is a member of the background process group - // http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html - sigemptyset(&sigmask); - sigaddset(&sigmask, SIGTTOU); - sigprocmask(SIG_BLOCK,&sigmask, &original_sigmask); - tcsetattr(0, TCSAFLUSH, &rtt); - sigprocmask(SIG_SETMASK, &original_sigmask, 0); - } - } - - // Fork dpkg - pid_t Child; - _config->Set("APT::Keep-Fds::",fd[1]); - // send status information that we are about to fork dpkg - if(OutStatusFd > 0) { - ostringstream status; - status << "pmstatus:dpkg-exec:" - << (PackagesDone/float(PackagesTotal)*100.0) - << ":" << _("Running dpkg") - << endl; - write(OutStatusFd, status.str().c_str(), status.str().size()); - } - Child = ExecFork(); - - // This is the child + // now run dpkg + d->progress->StartDpkg(); + std::set KeepFDs; + KeepFDs.insert(fd[1]); + MergeKeepFdsFromConfiguration(KeepFDs); + pid_t Child = ExecFork(KeepFDs); if (Child == 0) { - if(slave >= 0 && master >= 0) + // This is the child + if(d->slave >= 0 && d->master >= 0) { setsid(); - ioctl(slave, TIOCSCTTY, 0); - close(master); - dup2(slave, 0); - dup2(slave, 1); - dup2(slave, 2); - close(slave); + ioctl(d->slave, TIOCSCTTY, 0); + close(d->master); + dup2(d->slave, 0); + dup2(d->slave, 1); + dup2(d->slave, 2); + close(d->slave); } close(fd[0]); // close the read end of the pipe - if (_config->FindDir("DPkg::Chroot-Directory","/") != "/") - { - std::cerr << "Chrooting into " - << _config->FindDir("DPkg::Chroot-Directory") - << std::endl; - if (chroot(_config->FindDir("DPkg::Chroot-Directory","/").c_str()) != 0) - _exit(100); - } + dpkgChrootDirectory(); if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0) _exit(100); - + if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO)) { int Flags,dummy; @@ -1009,7 +1426,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) /* No Job Control Stop Env is a magic dpkg var that prevents it from using sigstop */ putenv((char *)"DPKG_NO_TSTP=yes"); - execvp(Args[0],(char **)Args); + execvp(Args[0], (char**) &Args[0]); cerr << "Could not exec dpkg!" << endl; _exit(100); } @@ -1018,9 +1435,6 @@ bool pkgDPkgPM::Go(int OutStatusFd) if (_config->FindB("DPkg::UseIoNice", false) == true) ionice(Child); - // clear the Keep-Fd again - _config->Clear("APT::Keep-Fds",fd[1]); - // Wait for dpkg int Status = 0; @@ -1028,12 +1442,15 @@ bool pkgDPkgPM::Go(int OutStatusFd) int const _dpkgin = fd[0]; close(fd[1]); // close the write end of the pipe - if(slave > 0) - close(slave); - // setups fds - sigemptyset(&sigmask); - sigprocmask(SIG_BLOCK,&sigmask,&original_sigmask); + sigemptyset(&d->sigmask); + sigprocmask(SIG_BLOCK,&d->sigmask,&d->original_sigmask); + + /* free vectors (and therefore memory) as we don't need the included data anymore */ + for (std::vector::const_iterator p = Packages.begin(); + p != Packages.end(); ++p) + free(*p); + Packages.clear(); // the result of the waitpid call int res; @@ -1049,24 +1466,26 @@ bool pkgDPkgPM::Go(int OutStatusFd) // Restore sig int/quit signal(SIGQUIT,old_SIGQUIT); signal(SIGINT,old_SIGINT); + signal(SIGHUP,old_SIGHUP); return _error->Errno("waitpid","Couldn't wait for subprocess"); } // wait for input or output here FD_ZERO(&rfds); - if (!stdin_is_dev_null) + if (d->master >= 0 && !d->stdin_is_dev_null) FD_SET(0, &rfds); FD_SET(_dpkgin, &rfds); - if(master >= 0) - FD_SET(master, &rfds); - tv.tv_sec = 1; - tv.tv_nsec = 0; - select_ret = pselect(max(master, _dpkgin)+1, &rfds, NULL, NULL, - &tv, &original_sigmask); + if(d->master >= 0) + FD_SET(d->master, &rfds); + tv.tv_sec = 0; + tv.tv_nsec = d->progress->GetPulseInterval(); + select_ret = pselect(max(d->master, _dpkgin)+1, &rfds, NULL, NULL, + &tv, &d->original_sigmask); if (select_ret < 0 && (errno == EINVAL || errno == ENOSYS)) - select_ret = racy_pselect(max(master, _dpkgin)+1, &rfds, NULL, - NULL, &tv, &original_sigmask); + select_ret = racy_pselect(max(d->master, _dpkgin)+1, &rfds, NULL, + NULL, &tv, &d->original_sigmask); + d->progress->Pulse(); if (select_ret == 0) continue; else if (select_ret < 0 && errno == EINTR) @@ -1077,26 +1496,20 @@ bool pkgDPkgPM::Go(int OutStatusFd) continue; } - if(master >= 0 && FD_ISSET(master, &rfds)) - DoTerminalPty(master); - if(master >= 0 && FD_ISSET(0, &rfds)) - DoStdin(master); + if(d->master >= 0 && FD_ISSET(d->master, &rfds)) + DoTerminalPty(d->master); + if(d->master >= 0 && FD_ISSET(0, &rfds)) + DoStdin(d->master); if(FD_ISSET(_dpkgin, &rfds)) - DoDpkgStatusFd(_dpkgin, OutStatusFd); + DoDpkgStatusFd(_dpkgin); } close(_dpkgin); // Restore sig int/quit signal(SIGQUIT,old_SIGQUIT); signal(SIGINT,old_SIGINT); + signal(SIGHUP,old_SIGHUP); - - if(master >= 0) - { - tcsetattr(0, TCSAFLUSH, &tt); - close(master); - } - // Check for an error code. if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) { @@ -1109,30 +1522,59 @@ bool pkgDPkgPM::Go(int OutStatusFd) RunScripts("DPkg::Post-Invoke"); if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) - strprintf(dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]); + strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]); else if (WIFEXITED(Status) != 0) - strprintf(dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); + strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); else - strprintf(dpkg_error, "Sub-process %s exited unexpectedly",Args[0]); + strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]); - if(dpkg_error.size() > 0) - _error->Error(dpkg_error.c_str()); + if(d->dpkg_error.size() > 0) + _error->Error("%s", d->dpkg_error.c_str()); if(stopOnError) { CloseLog(); + StopPtyMagic(); + d->progress->Stop(); return false; } } } + // dpkg is done at this point + d->progress->Stop(); + StopPtyMagic(); CloseLog(); + if (pkgPackageManager::SigINTStop) + _error->Warning(_("Operation was interrupted before it could finish")); + if (RunScripts("DPkg::Post-Invoke") == false) return false; + if (_config->FindB("Debug::pkgDPkgPM",false) == false) + { + std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache"); + if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true && + unlink(oldpkgcache.c_str()) == 0) + { + std::string const srcpkgcache = _config->FindFile("Dir::cache::srcpkgcache"); + if (srcpkgcache.empty() == false && RealFileExists(srcpkgcache) == true) + { + _error->PushToStack(); + pkgCacheFile CacheFile; + CacheFile.BuildCaches(NULL, true); + _error->RevertToStack(); + } + } + } + Cache.writeStateFile(NULL); return true; } + +void SigINT(int sig) { + pkgPackageManager::SigINTStop = true; +} /*}}}*/ // pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/ // --------------------------------------------------------------------- @@ -1142,3 +1584,241 @@ void pkgDPkgPM::Reset() List.erase(List.begin(),List.end()); } /*}}}*/ +// pkgDpkgPM::WriteApportReport - write out error report pkg failure /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) +{ + // If apport doesn't exist or isn't installed do nothing + // This e.g. prevents messages in 'universes' without apport + pkgCache::PkgIterator apportPkg = Cache.FindPkg("apport"); + if (apportPkg.end() == true || apportPkg->CurrentVer == 0) + return; + + string pkgname, reportfile, srcpkgname, pkgver, arch; + string::size_type pos; + FILE *report; + + if (_config->FindB("Dpkg::ApportFailureReport", false) == false) + { + std::clog << "configured to not write apport reports" << std::endl; + return; + } + + // only report the first errors + if(pkgFailures > _config->FindI("APT::Apport::MaxReports", 3)) + { + std::clog << _("No apport report written because MaxReports is reached already") << std::endl; + return; + } + + // check if its not a follow up error + const char *needle = dgettext("dpkg", "dependency problems - leaving unconfigured"); + if(strstr(errormsg, needle) != NULL) { + std::clog << _("No apport report written because the error message indicates its a followup error from a previous failure.") << std::endl; + return; + } + + // do not report disk-full failures + if(strstr(errormsg, strerror(ENOSPC)) != NULL) { + std::clog << _("No apport report written because the error message indicates a disk full error") << std::endl; + return; + } + + // do not report out-of-memory failures + if(strstr(errormsg, strerror(ENOMEM)) != NULL || + strstr(errormsg, "failed to allocate memory") != NULL) { + std::clog << _("No apport report written because the error message indicates a out of memory error") << std::endl; + return; + } + + // do not report bugs regarding inaccessible local files + if(strstr(errormsg, strerror(ENOENT)) != NULL || + strstr(errormsg, "cannot access archive") != NULL) { + std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl; + return; + } + + // do not report errors encountered when decompressing packages + if(strstr(errormsg, "--fsys-tarfile returned error exit status 2") != NULL) { + std::clog << _("No apport report written because the error message indicates an issue on the local system") << std::endl; + return; + } + + // do not report dpkg I/O errors, this is a format string, so we compare + // the prefix and the suffix of the error with the dpkg error message + vector io_errors; + io_errors.push_back(string("failed to read on buffer copy for %s")); + io_errors.push_back(string("failed in write on buffer copy for %s")); + io_errors.push_back(string("short read on buffer copy for %s")); + + for (vector::iterator I = io_errors.begin(); I != io_errors.end(); I++) + { + vector list = VectorizeString(dgettext("dpkg", (*I).c_str()), '%'); + if (list.size() > 1) { + // we need to split %s, VectorizeString only allows char so we need + // to kill the "s" manually + if (list[1].size() > 1) { + list[1].erase(0, 1); + if(strstr(errormsg, list[0].c_str()) && + strstr(errormsg, list[1].c_str())) { + std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl; + return; + } + } + } + } + + // get the pkgname and reportfile + pkgname = flNotDir(pkgpath); + pos = pkgname.find('_'); + if(pos != string::npos) + pkgname = pkgname.substr(0, pos); + + // find the package versin and source package name + pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); + if (Pkg.end() == true) + return; + pkgCache::VerIterator Ver = Cache.GetCandidateVer(Pkg); + if (Ver.end() == true) + return; + pkgver = Ver.VerStr() == NULL ? "unknown" : Ver.VerStr(); + pkgRecords Recs(Cache); + pkgRecords::Parser &Parse = Recs.Lookup(Ver.FileList()); + srcpkgname = Parse.SourcePkg(); + if(srcpkgname.empty()) + srcpkgname = pkgname; + + // if the file exists already, we check: + // - if it was reported already (touched by apport). + // If not, we do nothing, otherwise + // we overwrite it. This is the same behaviour as apport + // - if we have a report with the same pkgversion already + // then we skip it + reportfile = flCombine("/var/crash",pkgname+".0.crash"); + if(FileExists(reportfile)) + { + struct stat buf; + char strbuf[255]; + + // check atime/mtime + stat(reportfile.c_str(), &buf); + if(buf.st_mtime > buf.st_atime) + return; + + // check if the existing report is the same version + report = fopen(reportfile.c_str(),"r"); + while(fgets(strbuf, sizeof(strbuf), report) != NULL) + { + if(strstr(strbuf,"Package:") == strbuf) + { + char pkgname[255], version[255]; + if(sscanf(strbuf, "Package: %254s %254s", pkgname, version) == 2) + if(strcmp(pkgver.c_str(), version) == 0) + { + fclose(report); + return; + } + } + } + fclose(report); + } + + // now write the report + arch = _config->Find("APT::Architecture"); + report = fopen(reportfile.c_str(),"w"); + if(report == NULL) + return; + if(_config->FindB("DPkgPM::InitialReportOnly",false) == true) + chmod(reportfile.c_str(), 0); + else + chmod(reportfile.c_str(), 0600); + fprintf(report, "ProblemType: Package\n"); + fprintf(report, "Architecture: %s\n", arch.c_str()); + time_t now = time(NULL); + fprintf(report, "Date: %s" , ctime(&now)); + fprintf(report, "Package: %s %s\n", pkgname.c_str(), pkgver.c_str()); + fprintf(report, "SourcePackage: %s\n", srcpkgname.c_str()); + fprintf(report, "ErrorMessage:\n %s\n", errormsg); + + // ensure that the log is flushed + if(d->term_out) + fflush(d->term_out); + + // attach terminal log it if we have it + string logfile_name = _config->FindFile("Dir::Log::Terminal"); + if (!logfile_name.empty()) + { + FILE *log = NULL; + + fprintf(report, "DpkgTerminalLog:\n"); + log = fopen(logfile_name.c_str(),"r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + fprintf(report, " \n"); + fclose(log); + } + } + + // attach history log it if we have it + string histfile_name = _config->FindFile("Dir::Log::History"); + if (!histfile_name.empty()) + { + FILE *log = NULL; + char buf[1024]; + + fprintf(report, "DpkgHistoryLog:\n"); + log = fopen(histfile_name.c_str(),"r"); + if(log != NULL) + { + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + fclose(log); + } + } + + // log the ordering + const char *ops_str[] = {"Install", "Configure","Remove","Purge"}; + fprintf(report, "AptOrdering:\n"); + for (vector::iterator I = List.begin(); I != List.end(); ++I) + if ((*I).Pkg != NULL) + fprintf(report, " %s: %s\n", (*I).Pkg.Name(), ops_str[(*I).Op]); + else + fprintf(report, " %s: %s\n", "NULL", ops_str[(*I).Op]); + + // attach dmesg log (to learn about segfaults) + if (FileExists("/bin/dmesg")) + { + fprintf(report, "Dmesg:\n"); + FILE *log = popen("/bin/dmesg","r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + pclose(log); + } + } + + // attach df -l log (to learn about filesystem status) + if (FileExists("/bin/df")) + { + + fprintf(report, "Df:\n"); + FILE *log = popen("/bin/df -l","r"); + if(log != NULL) + { + char buf[1024]; + while( fgets(buf, sizeof(buf), log) != NULL) + fprintf(report, " %s", buf); + pclose(log); + } + } + + fclose(report); + +} + /*}}}*/