X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/0dbb95d810308d8dd102fba0303eed657f9f1cd2..bedb5293c16d105966d9bf48951483af0fbaff2a:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index b9c8cf968..cebdafe7d 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.5 1998/12/14 06:54:44 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 @@ -22,12 +24,20 @@ #include #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) { } /*}}}*/ @@ -65,35 +75,377 @@ bool pkgDPkgPM::Configure(PkgIterator Pkg) // DPkgPM::Remove - Remove a package /*{{{*/ // --------------------------------------------------------------------- /* Add a remove operation to the sequence list */ -bool pkgDPkgPM::Remove(PkgIterator Pkg) +bool pkgDPkgPM::Remove(PkgIterator Pkg,bool Purge) { if (Pkg.end() == true) return false; - List.push_back(Item(Item::Remove,Pkg)); + if (Purge == true) + List.push_back(Item(Item::Purge,Pkg)); + else + List.push_back(Item(Item::Remove,Pkg)); + return true; +} + /*}}}*/ +// DPkgPM::RunScripts - Run a set of scripts /*{{{*/ +// --------------------------------------------------------------------- +/* This looks for a list of script sto run from the configuration file, + each one is run with system from a forked child. */ +bool pkgDPkgPM::RunScripts(const char *Cnf) +{ + Configuration::Item const *Opts = _config->Tree(Cnf); + if (Opts == 0 || Opts->Child == 0) + return true; + Opts = Opts->Child; + + // Fork for running the system calls + pid_t Child = ExecFork(); + + // This is the child + if (Child == 0) + { + if (chdir("/tmp/") != 0) + _exit(100); + + unsigned int Count = 1; + for (; Opts != 0; Opts = Opts->Next, Count++) + { + if (Opts->Value.empty() == true) + continue; + + if (system(Opts->Value.c_str()) != 0) + _exit(100+Count); + } + _exit(0); + } + + // Wait for the child + int Status = 0; + while (waitpid(Child,&Status,0) != Child) + { + if (errno == EINTR) + continue; + return _error->Errno("waitpid","Couldn't wait for subprocess"); + } + + // Restore sig int/quit + signal(SIGQUIT,SIG_DFL); + signal(SIGINT,SIG_DFL); + + // Check for an error code. + if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) + { + unsigned int Count = WEXITSTATUS(Status); + if (Count > 100) + { + Count -= 100; + for (; Opts != 0 && Count != 1; Opts = Opts->Next, Count--); + _error->Error("Problem executing scripts %s '%s'",Cnf,Opts->Value.c_str()); + } + + return _error->Error("Sub-process returned an error code"); + } + + 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 + 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); + + 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. + bool Die = false; + if (Version <= 1) + { + for (vector::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) + { + Die = true; + break; + } + } + } + else + Die = !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::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 + Configuration::Item const *Opts = _config->Tree("DPkg::Options"); + if (Opts != 0) + { + Opts = Opts->Child; + for (; Opts != 0; Opts = Opts->Next) + { + if (Opts->Value.empty() == true) + continue; + Args[n++] = Opts->Value.c_str(); + Size += Opts->Value.length(); + } + } + + 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: @@ -105,6 +457,15 @@ bool pkgDPkgPM::Go() Size += strlen(Args[n-1]); break; + case Item::Purge: + Args[n++] = "--force-depends"; + Size += strlen(Args[n-1]); + Args[n++] = "--force-remove-essential"; + Size += strlen(Args[n-1]); + Args[n++] = "--purge"; + Size += strlen(Args[n-1]); + break; + case Item::Configure: Args[n++] = "--configure"; Size += strlen(Args[n-1]); @@ -113,21 +474,25 @@ bool pkgDPkgPM::Go() case Item::Install: Args[n++] = "--unpack"; Size += strlen(Args[n-1]); + Args[n++] = "--auto-deconfigure"; + Size += strlen(Args[n-1]); break; } // 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()); Args[n++] = I->File.c_str(); Size += strlen(Args[n-1]); } } else { - for (;I != J && Size < 1024; I++) + for (;I != J && Size < MaxArgBytes; I++) { Args[n++] = I->Pkg.Name(); Size += strlen(Args[n-1]); @@ -152,69 +517,232 @@ 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 = fork(); - if (Child < 0) - return _error->Errno("fork","Could't fork"); - + 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) { - signal(SIGPIPE,SIG_DFL); - signal(SIGQUIT,SIG_DFL); - signal(SIGINT,SIG_DFL); - signal(SIGWINCH,SIG_DFL); - signal(SIGCONT,SIG_DFL); - signal(SIGTSTP,SIG_DFL); - - if (chdir(_config->FindDir("Dir::Cache::Archives").c_str()) != 0) - _exit(100); + close(fd[0]); // close the read end of the pipe - // Close all of our FDs - just in case - for (int K = 3; K != 40; K++) - fcntl(K,F_SETFD,FD_CLOEXEC); - - 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 */ - setenv("DPKG_NO_TSTP","yes",1); + putenv("DPKG_NO_TSTP=yes"); execvp(Args[0],(char **)Args); cerr << "Could not exec dpkg!" << endl; _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; - 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,old_SIGQUIT); + signal(SIGINT,old_SIGINT); + // Check for an error code. if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0) - return _error->Error("Sub-process returned an error code"); + { + // 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); + + if(stopOnError) + RunScripts("DPkg::Post-Invoke"); - // Restore sig int/quit - signal(SIGQUIT,SIG_DFL); - signal(SIGINT,SIG_DFL); + 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; + } } + + if (RunScripts("DPkg::Post-Invoke") == false) + return false; return true; } /*}}}*/ +// pkgDpkgPM::Reset - Dump the contents of the command list /*{{{*/ +// --------------------------------------------------------------------- +/* */ +void pkgDPkgPM::Reset() +{ + List.erase(List.begin(),List.end()); +} + /*}}}*/