+ if (Purge == true)
+ List.push_back(Item(Item::Purge,Pkg));
+ else
+ List.push_back(Item(Item::Remove,Pkg));
+ return true;
+}
+ /*}}}*/
+// DPkgPM::SendV2Pkgs - Send version 2 package info /*{{{*/
+// ---------------------------------------------------------------------
+/* 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
+ configuration tree */
+ const Configuration::Item *Top = _config->Tree(0);
+ for (; Top != 0;)
+ {
+ if (Top->Value.empty() == false)
+ {
+ fprintf(F,"%s=%s\n",
+ QuoteString(Top->FullTag(),"=\"\n").c_str(),
+ QuoteString(Top->Value,"\n").c_str());
+ }
+
+ if (Top->Child != 0)
+ {
+ Top = Top->Child;
+ continue;
+ }
+
+ while (Top != 0 && Top->Next == 0)
+ Top = Top->Parent;
+ if (Top != 0)
+ Top = Top->Next;
+ }
+ fprintf(F,"\n");
+
+ // Write out the package actions in order.
+ for (vector<Item>::iterator I = List.begin(); I != List.end(); I++)
+ {
+ if(I->Pkg.end() == true)
+ continue;
+
+ pkgDepCache::StateCache &S = Cache[I->Pkg];
+
+ fprintf(F,"%s ",I->Pkg.Name());
+ // Current version
+ if (I->Pkg->CurrentVer == 0)
+ fprintf(F,"- ");
+ else
+ fprintf(F,"%s ",I->Pkg.CurrentVer().VerStr());
+
+ // Show the compare operator
+ // Target version
+ if (S.InstallVer != 0)
+ {
+ int Comp = 2;
+ if (I->Pkg->CurrentVer != 0)
+ Comp = S.InstVerIter(Cache).CompareVer(I->Pkg.CurrentVer());
+ if (Comp < 0)
+ fprintf(F,"> ");
+ if (Comp == 0)
+ fprintf(F,"= ");
+ if (Comp > 0)
+ fprintf(F,"< ");
+ fprintf(F,"%s ",S.InstVerIter(Cache).VerStr());
+ }
+ else
+ fprintf(F,"> - ");
+
+ // Show the filename/operation
+ if (I->Op == Item::Install)
+ {
+ // No errors here..
+ if (I->File[0] != '/')
+ fprintf(F,"**ERROR**\n");
+ else
+ fprintf(F,"%s\n",I->File.c_str());
+ }
+ if (I->Op == Item::Configure)
+ fprintf(F,"**CONFIGURE**\n");
+ if (I->Op == Item::Remove ||
+ I->Op == Item::Purge)
+ fprintf(F,"**REMOVE**\n");
+
+ if (ferror(F) != 0)
+ return false;
+ }
+ return true;
+}
+ /*}}}*/
+// DPkgPM::RunScriptsWithPkgs - Run scripts with package names on stdin /*{{{*/
+// ---------------------------------------------------------------------
+/* This looks for a list of scripts to run from the configuration file
+ each one is run and is fed on standard input a list of all .deb files
+ that are due to be installed. */
+bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf)
+{
+ Configuration::Item const *Opts = _config->Tree(Cnf);
+ if (Opts == 0 || Opts->Child == 0)
+ return true;
+ Opts = Opts->Child;
+
+ unsigned int Count = 1;
+ for (; Opts != 0; Opts = Opts->Next, Count++)
+ {
+ if (Opts->Value.empty() == true)
+ continue;
+
+ // Determine the protocol version
+ string OptSec = Opts->Value;
+ string::size_type Pos;
+ if ((Pos = OptSec.find(' ')) == string::npos || Pos == 0)
+ Pos = OptSec.length();
+ OptSec = "DPkg::Tools::Options::" + string(Opts->Value.c_str(),Pos);
+
+ unsigned int Version = _config->FindI(OptSec+"::Version",1);
+
+ // 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);
+ SetCloseExec(Pipes[1],true);
+
+ // Purified Fork for running the script
+ pid_t Process = ExecFork();
+ if (Process == 0)
+ {
+ // Setup the FDs
+ dup2(Pipes[0],STDIN_FILENO);
+ SetCloseExec(STDOUT_FILENO,false);
+ SetCloseExec(STDIN_FILENO,false);
+ SetCloseExec(STDERR_FILENO,false);
+
+ 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);
+ }
+
+ const char *Args[4];
+ Args[0] = "/bin/sh";
+ Args[1] = "-c";
+ Args[2] = Opts->Value.c_str();
+ Args[3] = 0;
+ execv(Args[0],(char **)Args);
+ _exit(100);
+ }
+ close(Pipes[0]);
+ FILE *F = fdopen(Pipes[1],"w");
+ if (F == 0)
+ return _error->Errno("fdopen","Faild to open new FD");
+
+ // Feed it the filenames.
+ if (Version <= 1)
+ {
+ for (vector<Item>::iterator I = List.begin(); I != List.end(); I++)
+ {
+ // Only deal with packages to be installed from .deb
+ if (I->Op != Item::Install)
+ continue;
+
+ // No errors here..
+ if (I->File[0] != '/')
+ continue;
+
+ /* Feed the filename of each package that is pending install
+ into the pipe. */
+ fprintf(F,"%s\n",I->File.c_str());
+ if (ferror(F) != 0)
+ break;
+ }
+ }
+ else
+ SendV2Pkgs(F);
+
+ 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());
+ }
+
+ return true;
+}
+ /*}}}*/
+// DPkgPM::DoStdin - Read stdin and pass to slave pty /*{{{*/
+// ---------------------------------------------------------------------
+/*
+*/
+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);
+ else
+ d->stdin_is_dev_null = true;
+}
+ /*}}}*/
+// DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/
+// ---------------------------------------------------------------------
+/*
+ * read the terminal pty and write log
+ */
+void pkgDPkgPM::DoTerminalPty(int master)
+{
+ unsigned char term_buf[1024] = {0,0, };
+
+ ssize_t len=read(master, term_buf, sizeof(term_buf));
+ if(len == -1 && errno == EIO)
+ {
+ // this happens when the child is about to exit, we
+ // give it time to actually exit, otherwise we run
+ // 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(d->term_out)
+ fwrite(term_buf, len, sizeof(char), d->term_out);
+}
+ /*}}}*/
+// DPkgPM::ProcessDpkgStatusBuf /*{{{*/
+// ---------------------------------------------------------------------
+/*
+ */
+void pkgDPkgPM::ProcessDpkgStatusLine(int OutStatusFd, 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: <pkg>: <pkg qstate>'
+ 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'
+
+ */
+ 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)
+ {
+ if (Debug == true)
+ std::clog << "ignoring line: not enough ':'" << std::endl;
+ return;
+ }
+ const char* const pkg = list[1];
+ const char* action = _strstrip(list[2]);
+
+ // 'processing' from dpkg looks like
+ // 'processing: action: pkg'
+ if(strncmp(list[0], "processing", strlen("processing")) == 0)
+ {
+ char s[200];
+ const char* const pkg_or_trigger = _strstrip(list[2]);
+ action = _strstrip( list[1]);
+ const std::pair<const char *, const char *> * const iter =
+ std::find_if(PackageProcessingOpsBegin,
+ PackageProcessingOpsEnd,
+ MatchProcessingOp(action));
+ 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]);
+ 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<struct DpkgState> 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))
+ {
+ // 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 (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;
+ // 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)
+ {
+ 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;
+ }
+ }
+}
+ /*}}}*/
+// DPkgPM::DoDpkgStatusFd /*{{{*/
+// ---------------------------------------------------------------------
+/*
+ */
+void pkgDPkgPM::DoDpkgStatusFd(int statusfd, int OutStatusFd)
+{
+ char *p, *q;
+ int 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 = d->dpkgbuf;
+ while((q=(char*)memchr(p, '\n', d->dpkgbuf+d->dpkgbuf_pos-p)) != NULL)
+ {
+ *q = 0;
+ ProcessDpkgStatusLine(OutStatusFd, 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 d->dpkgbuf_pos
+ p = (char*)memrchr(d->dpkgbuf, 0, d->dpkgbuf_pos);
+ if(p == NULL)
+ return;
+
+ // we are interessted in the first char *after* 0x0
+ p++;
+
+ // move the unprocessed tail to the start and update pos
+ 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 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 const t = time(NULL);
+ struct tm const * const tmp = localtime(&t);
+ strftime(timestr, sizeof(timestr), "%F %T", tmp);
+
+ // open terminal log
+ string const logfile_name = flCombine(logdir,
+ _config->Find("Dir::Log::Terminal"));
+ if (!logfile_name.empty())
+ {
+ 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);
+ 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);
+ fprintf(d->term_out, "\nLog started: %s\n", timestr);
+ }
+
+ // write your history
+ string const history_name = flCombine(logdir,
+ _config->Find("Dir::Log::History"));
+ if (!history_name.empty())
+ {
+ 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());
+ chmod(history_name.c_str(), 0644);
+ 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++)
+ {
+ 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 (_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;
+}
+ /*}}}*/
+// DPkg::CloseLog /*{{{*/
+bool pkgDPkgPM::CloseLog()
+{
+ char timestr[200];
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+ strftime(timestr, sizeof(timestr), "%F %T", tmp);
+
+ if(d->term_out)
+ {
+ fprintf(d->term_out, "Log ended: ");
+ fprintf(d->term_out, "%s", timestr);
+ fprintf(d->term_out, "\n");
+ fclose(d->term_out);
+ }
+ d->term_out = NULL;
+
+ if(d->history_out)
+ {
+ if (disappearedPkgs.empty() == false)
+ {
+ string disappear;
+ for (std::set<std::string>::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;
+