X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/0410b3d54c2b59d761073207645f1a7fd2df8f5f..1979e742ad5e2a0b6e547fbe3f4c4066b5a9bd2e:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index a9982bc34..3204fc1bb 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -1,6 +1,6 @@ // -*- mode: cpp; mode: fold -*- // Description /*{{{*/ -// $Id: dpkgpm.cc,v 1.16 1999/12/12 03:48:36 jgg Exp $ +// $Id: dpkgpm.cc,v 1.28 2004/01/27 02:25:01 mdz Exp $ /* ###################################################################### DPKG Package Manager - Provide an interface to dpkg @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -23,12 +25,19 @@ #include #include #include +#include +#include + +#include +#include /*}}}*/ +using namespace std; + // DPkgPM::pkgDPkgPM - Constructor /*{{{*/ // --------------------------------------------------------------------- /* */ -pkgDPkgPM::pkgDPkgPM(pkgDepCache &Cache) : pkgPackageManager(Cache) +pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) : pkgPackageManager(Cache) { } /*}}}*/ @@ -139,8 +148,91 @@ bool pkgDPkgPM::RunScripts(const char *Cnf) 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::iterator I = List.begin(); I != List.end(); I++) + { + 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 @@ -158,7 +250,16 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) { 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) @@ -185,31 +286,38 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) _exit(100); } close(Pipes[0]); - FileFd Fd(Pipes[1]); - + FILE *F = fdopen(Pipes[1],"w"); + if (F == 0) + return _error->Errno("fdopen","Faild to open new FD"); + // Feed it the filenames. - for (vector::iterator I = List.begin(); I != List.end(); I++) + bool Die = false; + if (Version <= 1) { - // 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. */ - if (Fd.Write(I->File.begin(),I->File.length()) == false || - Fd.Write("\n",1) == false) + for (vector::iterator I = List.begin(); I != List.end(); I++) { - kill(Process,SIGINT); - Fd.Close(); - ExecWait(Process,Opts->Value.c_str(),true); - return _error->Error("Failure running script %s",Opts->Value.c_str()); + // 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) + { + Die = true; + break; + } } } - Fd.Close(); + else + Die = !SendV2Pkgs(F); + + fclose(F); // Clean up the sub process if (ExecWait(Process,Opts->Value.c_str()) == false) @@ -218,32 +326,100 @@ bool pkgDPkgPM::RunScriptsWithPkgs(const char *Cnf) return true; } - /*}}}*/ // DPkgPM::Go - Run the sequence /*{{{*/ // --------------------------------------------------------------------- -/* This globs the operations and calls dpkg */ -bool pkgDPkgPM::Go() +/* 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) { + unsigned int MaxArgs = _config->FindI("Dpkg::MaxArgs",8*1024); + unsigned int MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes",32*1024); + if (RunScripts("DPkg::Pre-Invoke") == false) return false; if (RunScriptsWithPkgs("DPkg::Pre-Install-Pkgs") == false) return false; + + // prepare the progress reporting + int Done = 0; + int Total = 0; + // 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[][5] = { + // 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} + }, + }; + + // the dpkg states that the pkg will run through, the string is + // the package, the vector contains the dpkg states that the package + // will go through + map > PackageOps; + // the dpkg states that are already done; the string is the package + // the int is the state that is already done (e.g. a package that is + // going to be install is already in state "half-installed") + map PackageOpsDone; + + // 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::iterator I = List.begin(); I != List.end();I++) + { + string 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]); + Total++; + } + } + // this loop is runs once per operation for (vector::iterator I = List.begin(); I != List.end();) { vector::iterator J = I; for (; J != List.end() && J->Op == I->Op; J++); // Generate the argument list - const char *Args[400]; - if (J - I > 350) - J = I + 350; + const char *Args[MaxArgs + 50]; + if (J - I > (signed)MaxArgs) + J = I + MaxArgs; unsigned int n = 0; unsigned long Size = 0; - Args[n++] = _config->Find("Dir::Bin::dpkg","dpkg").c_str(); + string Tmp = _config->Find("Dir::Bin::dpkg","dpkg"); + Args[n++] = Tmp.c_str(); Size += strlen(Args[n-1]); // Stick in any custom dpkg options @@ -260,6 +436,16 @@ bool pkgDPkgPM::Go() } } + char status_fd_buf[20]; + int fd[2]; + pipe(fd); + + Args[n++] = "--status-fd"; + Size += strlen(Args[n-1]); + snprintf(status_fd_buf,sizeof(status_fd_buf),"%i", fd[1]); + Args[n++] = status_fd_buf; + Size += strlen(Args[n-1]); + switch (I->Op) { case Item::Remove: @@ -294,7 +480,7 @@ bool pkgDPkgPM::Go() // Write in the file or package names if (I->Op == Item::Install) { - for (;I != J && Size < 1024; 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()); @@ -304,7 +490,7 @@ bool pkgDPkgPM::Go() } else { - for (;I != J && Size < 1024; I++) + for (;I != J && Size < MaxArgBytes; I++) { Args[n++] = I->Pkg.Name(); Size += strlen(Args[n-1]); @@ -329,31 +515,38 @@ bool pkgDPkgPM::Go() it forks scripts. What happens is that when you hit ctrl-c it sends 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 */ - signal(SIGQUIT,SIG_IGN); - signal(SIGINT,SIG_IGN); - - // Fork dpkg - pid_t Child = ExecFork(); + sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN); + sighandler_t old_SIGINT = signal(SIGINT,SIG_IGN); + + // Fork dpkg + pid_t Child; + _config->Set("APT::Keep-Fds::",fd[1]); + Child = ExecFork(); // This is the child if (Child == 0) { - if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0) - _exit(100); + close(fd[0]); // close the read end of the pipe - int Flags,dummy; - if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0) - _exit(100); - - // Discard everything in stdin before forking dpkg - if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0) + if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 0) _exit(100); - while (read(STDIN_FILENO,&dummy,1) == 1); + if (_config->FindB("DPkg::FlushSTDIN",true) == true && isatty(STDIN_FILENO)) + { + int Flags,dummy; + if ((Flags = fcntl(STDIN_FILENO,F_GETFL,dummy)) < 0) + _exit(100); + + // Discard everything in stdin before forking dpkg + if (fcntl(STDIN_FILENO,F_SETFL,Flags | O_NONBLOCK) < 0) + _exit(100); + + while (read(STDIN_FILENO,&dummy,1) == 1); + + if (fcntl(STDIN_FILENO,F_SETFL,Flags & (~(long)O_NONBLOCK)) < 0) + _exit(100); + } - 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("DPKG_NO_TSTP=yes"); @@ -362,31 +555,179 @@ bool pkgDPkgPM::Go() _exit(100); } + // clear the Keep-Fd again + _config->Clear("APT::Keep-Fds",fd[1]); + // Wait for dpkg int Status = 0; - while (waitpid(Child,&Status,0) != Child) - { - if (errno == EINTR) + + // we read from dpkg here + int _dpkgin = fd[0]; + fcntl(_dpkgin, F_SETFL, O_NONBLOCK); + close(fd[1]); // close the write end of the pipe + + // the read buffers for the communication with dpkg + char line[1024] = {0,}; + char buf[2] = {0,0}; + + // the result of the waitpid call + int res; + + while ((res=waitpid(Child,&Status, WNOHANG)) != Child) { + if(res < 0) { + // FIXME: move this to a function or something, looks ugly here + // error handling, waitpid returned -1 + if (errno == EINTR) + continue; + RunScripts("DPkg::Post-Invoke"); + + // Restore sig int/quit + signal(SIGQUIT,old_SIGQUIT); + signal(SIGINT,old_SIGINT); + return _error->Errno("waitpid","Couldn't wait for subprocess"); + } + + // read a single char, make sure that the read can't block + // (otherwise we may leave zombies) + int len = read(_dpkgin, buf, 1); + + // nothing to read, wait a bit for more + if(len <= 0) + { + usleep(1000); continue; - RunScripts("DPkg::Post-Invoke"); - return _error->Errno("waitpid","Couldn't wait for subprocess"); + } + + // sanity check (should never happen) + if(strlen(line) >= sizeof(line)-10) + { + _error->Error("got a overlong line from dpkg: '%s'",line); + line[0]=0; + } + // append to line, check if we got a complete line + strcat(line, buf); + if(buf[0] != '\n') + continue; + + if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true) + std::clog << "got from dpkg '" << line << "'" << std::endl; + + // the status we output + ostringstream status; + + /* 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 + + */ + 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])); + char *pkg = list[1]; + char *action = _strstrip(list[2]); + if( pkg == NULL || action == NULL) + { + if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true) + std::clog << "ignoring line: not enough ':'" << std::endl; + // reset the line buffer + line[0]=0; + continue; + } + + if(strncmp(action,"error",strlen("error")) == 0) + { + status << "pmerror:" << list[1] + << ":" << (Done/float(Total)*100.0) + << ":" << list[3] + << endl; + if(OutStatusFd > 0) + write(OutStatusFd, status.str().c_str(), status.str().size()); + line[0]=0; + if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true) + std::clog << "send: '" << status.str() << "'" << endl; + continue; + } + if(strncmp(action,"conffile",strlen("conffile")) == 0) + { + status << "pmconffile:" << list[1] + << ":" << (Done/float(Total)*100.0) + << ":" << list[3] + << endl; + if(OutStatusFd > 0) + write(OutStatusFd, status.str().c_str(), status.str().size()); + line[0]=0; + if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true) + std::clog << "send: '" << status.str() << "'" << endl; + continue; + } + + vector &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]++; + Done++; + // build the status str + status << "pmstatus:" << pkg + << ":" << (Done/float(Total)*100.0) + << ":" << s + << endl; + if(OutStatusFd > 0) + write(OutStatusFd, status.str().c_str(), status.str().size()); + if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true) + std::clog << "send: '" << status.str() << "'" << endl; + + } + if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true) + std::clog << "(parsed from dpkg) pkg: " << pkg + << " action: " << action << endl; + + // reset the line buffer + line[0]=0; } + close(_dpkgin); // Restore sig int/quit - signal(SIGQUIT,SIG_DFL); - signal(SIGINT,SIG_DFL); + signal(SIGQUIT,old_SIGQUIT); + signal(SIGINT,old_SIGINT); // Check for an error code. if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) { - RunScripts("DPkg::Post-Invoke"); - if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) - return _error->Error("Sub-process %s recieved a segmentation fault.",Args[0]); - - if (WIFEXITED(Status) != 0) - return _error->Error("Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); + // if it was set to "keep-dpkg-runing" then we won't return + // here but keep the loop going and just report it as a error + // for later + bool stopOnError = _config->FindB("Dpkg::StopOnError",true); - return _error->Error("Sub-process %s exited unexpectedly",Args[0]); + if(stopOnError) + RunScripts("DPkg::Post-Invoke"); + + if (WIFSIGNALED(Status) != 0 && WTERMSIG(Status) == SIGSEGV) + _error->Error("Sub-process %s received a segmentation fault.",Args[0]); + else if (WIFEXITED(Status) != 0) + _error->Error("Sub-process %s returned an error code (%u)",Args[0],WEXITSTATUS(Status)); + else + _error->Error("Sub-process %s exited unexpectedly",Args[0]); + + if(stopOnError) + return false; } }