X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/b1803e01ec18a4946523f3c3d0cbff2f0347ff30..00354054c15b030b90aca8a79b62b4ab2844e3f0:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index 8aea2e1c8..98fb7581a 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -51,7 +53,13 @@ using namespace std; class pkgDPkgPMPrivate { public: - pkgDPkgPMPrivate() : dpkgbuf_pos(0), term_out(NULL), history_out(NULL) + pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0), + term_out(NULL), history_out(NULL), + progress(NULL) + { + dpkgbuf[0] = '\0'; + } + ~pkgDPkgPMPrivate() { } bool stdin_is_dev_null; @@ -61,6 +69,7 @@ public: FILE *term_out; FILE *history_out; string dpkg_error; + APT::Progress::PackageManager *progress; }; namespace @@ -123,6 +132,20 @@ 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() { @@ -132,6 +155,8 @@ static void dpkgChrootDirectory() std::cerr << "Chrooting into " << chrootDir << std::endl; if (chroot(chrootDir.c_str()) != 0) _exit(100); + if (chdir("/") != 0) + _exit(100); } /*}}}*/ @@ -145,11 +170,11 @@ static pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg) { pkgCache::VerIterator Ver; - for (Ver = Pkg.VersionList(); Ver.end() == false; 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++) + for (F = Vf.File(); F.end() == false; ++F) { if (F && F.Archive()) { @@ -185,7 +210,7 @@ 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. @@ -236,15 +261,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;) @@ -278,30 +311,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) { @@ -311,9 +365,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"); @@ -349,24 +403,32 @@ 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 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 + _config->Set("APT::Keep-Fds::", Pipes[0]); SetCloseExec(Pipes[1],true); - + // Purified Fork for running the script - pid_t Process = ExecFork(); + pid_t Process = ExecFork(); 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"; @@ -376,6 +438,8 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) execv(Args[0],(char **)Args); _exit(100); } + if (InfoFD == (unsigned)Pipes[0]) + _config->Clear("APT::Keep-Fds", Pipes[0]); close(Pipes[0]); FILE *F = fdopen(Pipes[1],"w"); if (F == 0) @@ -402,7 +466,7 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) } } else - SendV2Pkgs(F); + SendPkgsInfo(F, Version); fclose(F); @@ -423,7 +487,7 @@ 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 d->stdin_is_dev_null = true; } @@ -449,7 +513,7 @@ void pkgDPkgPM::DoTerminalPty(int master) } if(len <= 0) return; - write(1, term_buf, len); + FileFd::Write(1, term_buf, len); if(d->term_out) fwrite(term_buf, len, sizeof(char), d->term_out); } @@ -458,156 +522,185 @@ void pkgDPkgPM::DoTerminalPty(int master) // --------------------------------------------------------------------- /* */ -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' - 'processing: disappear: pkg' - 'processing: trigproc: trigger' - + 'processing: {install,configure,remove,purge,disappear,trigproc}: pkg' + 'processing: {install,configure,remove,purge,disappear,trigproc}: trigger' */ - char* list[6]; - // 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: conffile-prompt: conffile : '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") + { + 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; - - if (strncmp(action, "disappear", strlen("disappear")) == 0) - handleDisappearAction(pkg_or_trigger); - return; - } - - if(strncmp(action,"error",strlen("error")) == 0) - { - // urgs, sometime has ":" in its error string so that we - // end up with the error message split between list[3] - // and list[4], e.g. the message: - // "failed in buffer_write(fd) (10, ret=-1): backend dpkg-deb ..." - // concat them again - if( list[4] != NULL ) - list[3][strlen(list[3])] = ':'; - - 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; - pkgFailures++; - WriteApportReport(list[1], list[3]); + 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; - } - 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; - 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)) + if (prefix == "status") { - // 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; + 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; } - if (Debug == true) - std::clog << "(parsed from dpkg) pkg: " << pkg - << " action: " << action << endl; } /*}}}*/ // DPkgPM::handleDisappearAction /*{{{*/ void pkgDPkgPM::handleDisappearAction(string const &pkgname) { - // record the package name for display and stuff later - disappearedPkgs.insert(pkgname); - pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname); if (unlikely(Pkg.end() == true)) return; + + // 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; @@ -650,7 +743,7 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname) // --------------------------------------------------------------------- /* */ -void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) +void pkgDPkgPM::DoDpkgStatusFd(int statusfd) { char *p, *q; int len; @@ -665,7 +758,7 @@ void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd) 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 } @@ -718,13 +811,15 @@ bool pkgDPkgPM::OpenLog() 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); - struct passwd *pw; - struct group *gr; - pw = getpwnam("root"); - gr = getgrnam("adm"); - if (pw != NULL && gr != NULL) - chown(logfile_name.c_str(), pw->pw_uid, gr->gr_gid); - chmod(logfile_name.c_str(), 0644); + 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); } @@ -736,6 +831,7 @@ bool pkgDPkgPM::OpenLog() 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(d->history_out, "\nStart-Date: %s\n", timestr); string remove, purge, install, reinstall, upgrade, downgrade; @@ -828,6 +924,7 @@ bool pkgDPkgPM::CloseLog() return true; } /*}}}*/ + /*}}}*/ /*{{{*/ // This implements a racy version of pselect for those architectures // that don't have a working implementation. @@ -848,36 +945,81 @@ static int racy_pselect(int nfds, fd_set *readfds, fd_set *writefds, sigprocmask(SIG_SETMASK, &origmask, 0); return retval; } -/*}}}*/ + /*}}}*/ + +// 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] = { + // Install operation + { + {"half-installed", N_("Preparing %s")}, + {"unpacked", N_("Unpacking %s") }, + {NULL, NULL} + }, + // Configure operation + { + {"unpacked",N_("Preparing to configure %s") }, + {"half-configured", N_("Configuring %s") }, + { "installed", N_("Installed %s")}, + {NULL, NULL} + }, + // Remove operation + { + {"half-configured", N_("Preparing for removal of %s")}, + {"half-installed", N_("Removing %s")}, + {"config-files", N_("Removed %s")}, + {NULL, NULL} + }, + // Purge operation + { + {"config-files", N_("Preparing to completely remove %s")}, + {"not-installed", N_("Completely removed %s")}, + {NULL, NULL} + }, + }; + + // init the PackageOps map, go over the list of packages that + // 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) + { + if((*I).Pkg.end() == true) + continue; + + string const name = (*I).Pkg.FullName(); + PackageOpsDone[name] = 0; + for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i) + { + PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]); + PackagesTotal++; + } + } +} + /*}}}*/ // 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) + * + * 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. -*/ -bool pkgDPkgPM::Go(int OutStatusFd) + */ +bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) { pkgPackageManager::SigINTStop = false; + d->progress = progress; // Generate the base argument list for dpkg - std::vector Args; unsigned long StartSize = 0; - 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); - } - } - Args.push_back(Tmp.c_str()); - StartSize += Tmp.length(); + 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"); @@ -934,54 +1076,8 @@ bool pkgDPkgPM::Go(int OutStatusFd) if (_config->FindB("DPkg::ConfigurePending", SmartConf) == true) List.push_back(Item(Item::ConfigurePending, PkgIterator())); - // 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] = { - // Install operation - { - {"half-installed", N_("Preparing %s")}, - {"unpacked", N_("Unpacking %s") }, - {NULL, NULL} - }, - // Configure operation - { - {"unpacked",N_("Preparing to configure %s") }, - {"half-configured", N_("Configuring %s") }, - { "installed", N_("Installed %s")}, - {NULL, NULL} - }, - // Remove operation - { - {"half-configured", N_("Preparing for removal of %s")}, - {"half-installed", N_("Removing %s")}, - {"config-files", N_("Removed %s")}, - {NULL, NULL} - }, - // Purge operation - { - {"config-files", N_("Preparing to completely remove %s")}, - {"not-installed", N_("Completely removed %s")}, - {NULL, NULL} - }, - }; - - // init the PackageOps map, go over the list of packages that - // 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) - { - if((*I).Pkg.end() == true) - continue; - - string const name = (*I).Pkg.Name(); - PackageOpsDone[name] = 0; - for(int i=0; (DpkgStatesOpMap[(*I).Op][i]).state != NULL; ++i) - { - PackageOps[name].push_back(DpkgStatesOpMap[(*I).Op][i]); - PackagesTotal++; - } - } + // for the progress + BuildPackagesProgressMap(); d->stdin_is_dev_null = false; @@ -1003,8 +1099,9 @@ bool pkgDPkgPM::Go(int OutStatusFd) dpkgMultiArch = true; } - // this loop is runs once per operation - for (vector::const_iterator I = List.begin(); I != List.end();) + // go over each item + 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; @@ -1053,7 +1150,8 @@ bool pkgDPkgPM::Go(int OutStatusFd) } int fd[2]; - pipe(fd); + 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 @@ -1124,10 +1222,12 @@ bool pkgDPkgPM::Go(int OutStatusFd) { if((*I).Pkg.end() == true) continue; - if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.Name()) != disappearedPkgs.end()) + 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"))) + 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); @@ -1144,7 +1244,9 @@ bool pkgDPkgPM::Go(int OutStatusFd) } else PkgVer = Cache[I->Pkg].InstVerIter(Cache); - if (PkgVer.end() == false) + 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()); @@ -1197,16 +1299,13 @@ bool pkgDPkgPM::Go(int OutStatusFd) // if tcgetattr does not return zero there was a error // and we do not do any pty magic - if (tcgetattr(0, &tt) == 0) + _error->PushToStack(); + if (tcgetattr(STDOUT_FILENO, &tt) == 0) { - ioctl(0, TIOCGWINSZ, (char *)&win); - if (openpty(&master, &slave, NULL, &tt, &win) < 0) + ioctl(STDOUT_FILENO, 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); - if(d->term_out) - fprintf(d->term_out, "%s",s); + _error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); master = slave = -1; } else { struct termios rtt; @@ -1224,23 +1323,27 @@ bool pkgDPkgPM::Go(int OutStatusFd) sigprocmask(SIG_SETMASK, &original_sigmask, 0); } } - // Fork dpkg - pid_t Child; + // 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(); + + // this is the dpkg status-fd, we need to keep it _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(); - + + // Tell the progress that its starting and fork dpkg + // FIXME: this is called once per dpkg run which is *too often* + d->progress->Start(); + + pid_t Child = ExecFork(); // This is the child if (Child == 0) { + if(slave >= 0 && master >= 0) { setsid(); @@ -1257,7 +1360,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) 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; @@ -1273,7 +1376,6 @@ bool pkgDPkgPM::Go(int OutStatusFd) if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0) _exit(100); } - /* No Job Control Stop Env is a magic dpkg var that prevents it from using sigstop */ putenv((char *)"DPKG_NO_TSTP=yes"); @@ -1335,13 +1437,16 @@ bool pkgDPkgPM::Go(int OutStatusFd) FD_SET(_dpkgin, &rfds); if(master >= 0) FD_SET(master, &rfds); - tv.tv_sec = 1; - tv.tv_nsec = 0; + tv.tv_sec = 0; + tv.tv_nsec = d->progress->GetPulseInterval(); select_ret = pselect(max(master, _dpkgin)+1, &rfds, NULL, NULL, &tv, &original_sigmask); if (select_ret < 0 && (errno == EINVAL || errno == ENOSYS)) select_ret = racy_pselect(max(master, _dpkgin)+1, &rfds, NULL, NULL, &tv, &original_sigmask); + + d->progress->Pulse(); + if (select_ret == 0) continue; else if (select_ret < 0 && errno == EINTR) @@ -1357,7 +1462,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) if(master >= 0 && FD_ISSET(0, &rfds)) DoStdin(master); if(FD_ISSET(_dpkgin, &rfds)) - DoDpkgStatusFd(_dpkgin, OutStatusFd); + DoDpkgStatusFd(_dpkgin); } close(_dpkgin); @@ -1372,7 +1477,7 @@ bool pkgDPkgPM::Go(int OutStatusFd) tcsetattr(0, TCSAFLUSH, &tt); close(master); } - + // Check for an error code. if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) { @@ -1397,12 +1502,16 @@ bool pkgDPkgPM::Go(int OutStatusFd) if(stopOnError) { CloseLog(); + d->progress->Stop(); return false; } } } CloseLog(); - + + // dpkg is done at this point + d->progress->Stop(); + if (pkgPackageManager::SigINTStop) _error->Warning(_("Operation was interrupted before it could finish")); @@ -1540,7 +1649,7 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) if(strstr(strbuf,"Package:") == strbuf) { char pkgname[255], version[255]; - if(sscanf(strbuf, "Package: %s %s", pkgname, version) == 2) + if(sscanf(strbuf, "Package: %254s %254s", pkgname, version) == 2) if(strcmp(pkgver.c_str(), version) == 0) { fclose(report); @@ -1577,12 +1686,12 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) if (!logfile_name.empty()) { FILE *log = NULL; - char buf[1024]; 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); fclose(log); @@ -1593,18 +1702,19 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) const char *ops_str[] = {"Install", "Configure","Remove","Purge"}; fprintf(report, "AptOrdering:\n"); for (vector::iterator I = List.begin(); I != List.end(); ++I) - fprintf(report, " %s: %s\n", (*I).Pkg.Name(), ops_str[(*I).Op]); + 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")) { - FILE *log = NULL; - char buf[1024]; - fprintf(report, "Dmesg:\n"); - log = popen("/bin/dmesg","r"); + 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); @@ -1614,13 +1724,12 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) // attach df -l log (to learn about filesystem status) if (FileExists("/bin/df")) { - FILE *log = NULL; - char buf[1024]; fprintf(report, "Df:\n"); - log = popen("/bin/df -l","r"); + 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);