X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/dd640f3cece45693b57b49d90ba7cc4134577070..8c782efd93342c6119e8ba2ff6989b7a164b7f3d:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index 7fba04a30..8295a7c1e 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -10,40 +10,45 @@ // 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 -#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 /*}}}*/ @@ -55,14 +60,13 @@ class pkgDPkgPMPrivate public: pkgDPkgPMPrivate() : stdin_is_dev_null(false), dpkgbuf_pos(0), term_out(NULL), history_out(NULL), - last_reported_progress(0.0), progress(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]; @@ -70,9 +74,17 @@ public: FILE *term_out; FILE *history_out; string dpkg_error; - - float last_reported_progress; APT::Progress::PackageManager *progress; + + // pty stuff + struct termios tt; + int master; + int slave; + + // signals + sigset_t sigmask; + sigset_t original_sigmask; + }; namespace @@ -135,6 +147,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() { @@ -180,7 +206,7 @@ pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg) // --------------------------------------------------------------------- /* */ pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) - : pkgPackageManager(Cache), PackagesDone(0), PackagesTotal(0) + : pkgPackageManager(Cache), pkgFailures(0), PackagesDone(0), PackagesTotal(0) { d = new pkgDPkgPMPrivate(); } @@ -373,10 +399,14 @@ bool pkgDPkgPM::SendPkgsInfo(FILE * const F, unsigned int const &Version) that are due to be installed. */ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) { + bool result = true; + Configuration::Item const *Opts = _config->Tree(Cnf); if (Opts == 0 || Opts->Child == 0) return true; Opts = Opts->Child; + + sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN); unsigned int Count = 1; for (; Opts != 0; Opts = Opts->Next, Count++) @@ -384,6 +414,10 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) if (Opts->Value.empty() == true) continue; + if(_config->FindB("Debug::RunScripts", false) == true) + std::clog << "Running external script with list of all .deb file: '" + << Opts->Value << "'" << std::endl; + // Determine the protocol version string OptSec = Opts->Value; string::size_type Pos; @@ -395,17 +429,23 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) 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"); + if (pipe(Pipes) != 0) { + result = _error->Errno("pipe","Failed to create IPC pipe to subprocess"); + break; + } if (InfoFD != (unsigned)Pipes[0]) SetCloseExec(Pipes[0],true); else - _config->Set("APT::Keep-Fds::", Pipes[0]); + 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 @@ -427,12 +467,12 @@ 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) - return _error->Errno("fdopen","Faild to open new FD"); + if (F == 0) { + result = _error->Errno("fdopen","Faild to open new FD"); + break; + } // Feed it the filenames. if (Version <= 1) @@ -460,11 +500,14 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) fclose(F); // Clean up the sub process - if (ExecWait(Process,Opts->Value.c_str()) == false) - return _error->Error("Failure running script %s",Opts->Value.c_str()); + if (ExecWait(Process,Opts->Value.c_str()) == false) { + result = _error->Error("Failure running script %s",Opts->Value.c_str()); + break; + } } + signal(SIGPIPE, old_sigpipe); - return true; + return result; } /*}}}*/ // DPkgPM::DoStdin - Read stdin and pass to slave pty /*{{{*/ @@ -474,7 +517,7 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) void pkgDPkgPM::DoStdin(int master) { unsigned char input_buf[256] = {0,}; - ssize_t len = read(0, input_buf, sizeof(input_buf)); + ssize_t len = read(STDIN_FILENO, input_buf, sizeof(input_buf)); if (len) FileFd::Write(master, input_buf, len); else @@ -507,34 +550,6 @@ void pkgDPkgPM::DoTerminalPty(int master) fwrite(term_buf, len, sizeof(char), d->term_out); } /*}}}*/ - -std::string pkgDPkgPM::ExpandShortPackageName(pkgDepCache &Cache, - const std::string &short_pkgname) -{ - if (short_pkgname.find(":") != string::npos) - return short_pkgname; - - std::string pkgname = short_pkgname; - // 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].Install() || Cache[P].ReInstall() || - Cache[P].Upgrade() || Cache[P].Downgrade() || - Cache[P].Delete() || Cache[P].Purge()) - { - pkgname = P.FullName(); - return pkgname; - } - } - } - return pkgname; -} - // DPkgPM::ProcessDpkgStatusBuf /*{{{*/ // --------------------------------------------------------------------- /* @@ -542,28 +557,17 @@ std::string pkgDPkgPM::ExpandShortPackageName(pkgDepCache &Cache, void pkgDPkgPM::ProcessDpkgStatusLine(char *line) { bool const Debug = _config->FindB("Debug::pkgDPkgProgressReporting",false); - if (Debug == true) std::clog << "got from dpkg '" << line << "'" << std::endl; - /* dpkg sends strings like this: 'status: : ' '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 - 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' */ + // we need to split on ": " (note the appended space) as the ':' is // part of the pkgname:arch information that dpkg sends // @@ -578,45 +582,95 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line) return; } - if (list[0] != "processing" && list[0] != "status") + // 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; + + // "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 << "ignoring line: unknown prefix '" << list[0] << "'" - << std::endl; + std::clog << "unknown prefix '" << prefix << "'" << std::endl; return; } - std::string prefix = list[0]; - std::string pkgname; - std::string action; - if (prefix == "processing") + + /* 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") { - action = list[1]; - pkgname = list[2]; - if (action != "install" && action != "configure" && - action != "remove" && action != "purge" && - action != "disappear") + if(action == "error") + { + d->progress->Error(pkgname, PackagesDone, PackagesTotal, + list[3]); + pkgFailures++; + WriteApportReport(pkgname.c_str(), list[3].c_str()); return; - } else if (prefix == "status") { - pkgname = list[1]; - action = list[2]; + } + else if(action == "conffile-prompt") + { + d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, + list[3]); + return; + } } - // dpkg does not always send out the architecture so we need to guess - // it here - pkgname = ExpandShortPackageName(Cache, pkgname); + // 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 i18n_pkgname = short_pkgname; + std::string arch = ""; if (pkgname.find(":") != string::npos) - { - strprintf(i18n_pkgname, "%s (%s)", short_pkgname.c_str(), - StringSplit(pkgname, ":")[1].c_str()); - } + 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].c_str(), "processing", strlen("processing")) == 0) + if(prefix == "processing") { const std::pair * const iter = std::find_if(PackageProcessingOpsBegin, @@ -628,74 +682,56 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line) std::clog << "ignoring unknown action: " << action << std::endl; return; } - - std::string pkg_action; - strprintf(pkg_action, _(iter->second), short_pkgname.c_str()); - - d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, - pkg_action); - - if (strncmp(action.c_str(), "disappear", strlen("disappear")) == 0) + 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; - } - - // FIXME: fix indent once this goes into debian/sid - if(strncmp(prefix.c_str(), "status", strlen("processing")) == 0) - { + } - if(strncmp(action.c_str(),"error",strlen("error")) == 0) - { - d->progress->Error(list[1], PackagesDone, PackagesTotal, list[3]); - pkgFailures++; - WriteApportReport(list[1].c_str(), list[3].c_str()); - return; - } - else if(strncmp(action.c_str(),"conffile",strlen("conffile")) == 0) + if (prefix == "status") { - d->progress->ConffilePrompt(list[1], PackagesDone, PackagesTotal, - list[3]); - return; - } else { - - 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.c_str(), next_action) == 0)) - { - // only read the translation if there is actually a next - // action - std::string translation; - strprintf(translation, _(states[PackageOpsDone[pkg]].str), - i18n_pkgname.c_str()); - - // we moved from one dpkg state to a new one, report that - PackageOpsDone[pkg]++; - PackagesDone++; - - // and send to the progress - d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, - translation); - } - - if (Debug == true) - std::clog << "(parsed from dpkg) pkg: " << pkgname - << " action: " << action << 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; } } /*}}}*/ // 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; @@ -919,6 +955,8 @@ bool pkgDPkgPM::CloseLog() return true; } /*}}}*/ + /*}}}*/ +/*{{{*/ // This implements a racy version of pselect for those architectures // that don't have a working implementation. // FIXME: Probably can be removed on Lenny+1 @@ -938,39 +976,153 @@ 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++; + } + } +} + /*}}}*/ +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 Go(progress); +} +void pkgDPkgPM::StartPtyMagic() +{ + if (_config->FindB("Dpkg::Use-Pty", true) == false) + { + d->master = d->slave = -1; + return; + } + + // setup the pty and stuff + struct winsize win; + + // if tcgetattr for both stdin/stdout returns 0 (no error) + // we do the pty magic + _error->PushToStack(); + if (tcgetattr(STDIN_FILENO, &d->tt) == 0 && + tcgetattr(STDOUT_FILENO, &d->tt) == 0) + { + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win) < 0) + { + _error->Errno("ioctl", _("ioctl(TIOCGWINSZ) failed")); + } 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. -*/ + */ 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"); @@ -1007,8 +1159,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) 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); @@ -1027,54 +1177,8 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) 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.FullName(); - 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; @@ -1096,8 +1200,15 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) dpkgMultiArch = true; } - // this loop is runs once per operation - for (vector::const_iterator I = List.begin(); I != List.end();) + // 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; @@ -1218,7 +1329,7 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) { 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 || @@ -1288,67 +1399,29 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) // 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 - _error->PushToStack(); - if (tcgetattr(STDOUT_FILENO, &tt) == 0) - { - ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&win); - if (openpty(&master, &slave, NULL, &tt, &win) < 0) - { - _error->Errno("openpty", _("Can not write log (%s)"), _("Is /dev/pts mounted?")); - master = slave = -1; - } else { - struct termios rtt; - rtt = 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(&sigmask); - sigaddset(&sigmask, SIGTTOU); - sigprocmask(SIG_BLOCK,&sigmask, &original_sigmask); - tcsetattr(0, TCSAFLUSH, &rtt); - sigprocmask(SIG_SETMASK, &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(); - - // this is the dpkg status-fd, we need to keep it - _config->Set("APT::Keep-Fds::",fd[1]); - - // 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 + // 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); + int res = ioctl(d->slave, TIOCSCTTY, 0); + if (res < 0) { + std::cerr << "ioctl(TIOCSCTTY) failed for fd: " + << d->slave << std::endl; + } else { + 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 @@ -1359,7 +1432,8 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO)) { - int Flags,dummy; + int Flags; + int dummy = 0; if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0) _exit(100); @@ -1385,9 +1459,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) 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; @@ -1395,12 +1466,9 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) 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(); @@ -1429,20 +1497,19 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) // wait for input or output here FD_ZERO(&rfds); - if (master >= 0 && !d->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 = 0; - tv.tv_nsec = d->progress->GetPulseInterval(); - 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) @@ -1453,10 +1520,10 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) 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); } @@ -1467,13 +1534,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) 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) { @@ -1481,34 +1541,23 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) // here but keep the loop going and just report it as a error // for later bool const stopOnError = _config->FindB("Dpkg::StopOnError",true); - - if(stopOnError) - RunScripts("DPkg::Post-Invoke"); - if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) + if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) strprintf(d->dpkg_error, "Sub-process %s received a segmentation fault.",Args[0]); else if (WIFEXITED(Status) != 0) strprintf(d->dpkg_error, "Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); - else + else strprintf(d->dpkg_error, "Sub-process %s exited unexpectedly",Args[0]); + _error->Error("%s", d->dpkg_error.c_str()); - if(d->dpkg_error.size() > 0) - _error->Error("%s", d->dpkg_error.c_str()); - - if(stopOnError) - { - CloseLog(); - d->progress->Stop(); - return false; - } - } + if(stopOnError) + break; + } } - CloseLog(); - // dpkg is done at this point - d->progress->StatusChanged("", PackagesDone, PackagesTotal, ""); d->progress->Stop(); - + StopPtyMagic(); + CloseLog(); if (pkgPackageManager::SigINTStop) _error->Warning(_("Operation was interrupted before it could finish")); @@ -1534,10 +1583,10 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) } Cache.writeStateFile(NULL); - return true; + return d->dpkg_error.empty(); } -void SigINT(int sig) { +void SigINT(int /*sig*/) { pkgPackageManager::SigINTStop = true; } /*}}}*/ @@ -1564,7 +1613,7 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) string::size_type pos; FILE *report; - if (_config->FindB("Dpkg::ApportFailureReport", false) == false) + if (_config->FindB("Dpkg::ApportFailureReport", true) == false) { std::clog << "configured to not write apport reports" << std::endl; return; @@ -1591,18 +1640,50 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) } // do not report out-of-memory failures - if(strstr(errormsg, strerror(ENOMEM)) != NULL) { + 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 dpkg I/O errors - // XXX - this message is localized, but this only matches the English version. This is better than nothing. - if(strstr(errormsg, "short read in buffer_copy (")) { - std::clog << _("No apport report written because the error message indicates a dpkg I/O error") << std::endl; + // 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")); + io_errors.push_back(string("failed to write")); + io_errors.push_back(string("failed to seek")); + io_errors.push_back(string("unexpected end of file or stream")); + + 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('_'); @@ -1688,6 +1769,22 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg) 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()) + { + fprintf(report, "DpkgHistoryLog:\n"); + FILE* log = fopen(histfile_name.c_str(),"r"); + if(log != NULL) { char buf[1024]; while( fgets(buf, sizeof(buf), log) != NULL)