##################################################################### */
/*}}}*/
// Includes /*{{{*/
-#ifdef __GNUG__
-#pragma implementation "apt-pkg/dpkgpm.h"
-#endif
#include <apt-pkg/dpkgpm.h>
#include <apt-pkg/error.h>
#include <apt-pkg/configuration.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
+#include <sys/select.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
-#include <iostream>
+#include <sstream>
+#include <map>
+
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <pty.h>
+
+#include <config.h>
+#include <apti18n.h>
/*}}}*/
using namespace std;
// DPkgPM::pkgDPkgPM - Constructor /*{{{*/
// ---------------------------------------------------------------------
/* */
-pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) : pkgPackageManager(Cache)
+pkgDPkgPM::pkgDPkgPM(pkgDepCache *Cache) : pkgPackageManager(Cache), dpkgbuf_pos(0)
{
}
/*}}}*/
return true;
}
+
+ /*}}}*/
+// DPkgPM::DoStdin - Read stdin and pass to slave pty /*{{{*/
+// ---------------------------------------------------------------------
+/*
+*/
+void pkgDPkgPM::DoStdin(int master)
+{
+ char input_buf[256] = {0,};
+ int len = read(0, input_buf, sizeof(input_buf));
+ write(master, input_buf, len);
+}
+ /*}}}*/
+// DPkgPM::DoTerminalPty - Read the terminal pty and write log /*{{{*/
+// ---------------------------------------------------------------------
+/*
+ * read the terminal pty and write log
+ */
+void pkgDPkgPM::DoTerminalPty(int master, FILE *term_out)
+{
+ char term_buf[1024] = {0,};
+
+ int len=read(master, term_buf, sizeof(term_buf));
+ if(len <= 0)
+ return;
+ fwrite(term_buf, len, sizeof(char), term_out);
+ write(1, term_buf, len);
+}
/*}}}*/
+// DPkgPM::ProcessDpkgStatusBuf /*{{{*/
+// ---------------------------------------------------------------------
+/*
+ */
+void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
+{
+ std::cerr << "got line: '" << line << "'" << std::endl;
+}
+
+// DPkgPM::DoDpkgStatusFd /*{{{*/
+// ---------------------------------------------------------------------
+/*
+ */
+void pkgDPkgPM::DoDpkgStatusFd(int statusfd)
+{
+ char *p, *q;
+ int len;
+
+ len=read(statusfd, &dpkgbuf[dpkgbuf_pos], sizeof(dpkgbuf)-dpkgbuf_pos);
+ dpkgbuf_pos += len;
+ if(len <= 0)
+ return;
+
+ // process line by line if we have a buffer
+ p = q = dpkgbuf;
+ while((q=(char*)memchr(p, '\n', dpkgbuf+dpkgbuf_pos-p)) != NULL)
+ {
+ *q = 0;
+ ProcessDpkgStatusLine(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 dpkgbuf_pos
+ p = (char*)memrchr(dpkgbuf, 0, 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(dpkgbuf, p, p-dpkgbuf);
+ dpkgbuf_pos = dpkgbuf+dpkgbuf_pos-p;
+}
+ /*}}}*/
+
+
// DPkgPM::Go - Run the sequence /*{{{*/
// ---------------------------------------------------------------------
-/* This globs the operations and calls dpkg */
-bool pkgDPkgPM::Go(int status_fd)
+/* 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 (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<string,vector<struct DpkgState> > 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<string,int> 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<Item>::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<Item>::iterator I = List.begin(); I != List.end();)
{
vector<Item>::iterator J = I;
}
}
- // if we got a status_fd argument, we pass it to apt
char status_fd_buf[20];
- if(status_fd > 0)
- {
- Args[n++] = "--status-fd";
- Size += strlen(Args[n-1]);
- snprintf(status_fd_buf,20,"%i",status_fd);
- Args[n++] = status_fd_buf;
- Size += strlen(Args[n-1]);
- }
+ 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::Install:
Args[n++] = "--unpack";
Size += strlen(Args[n-1]);
+ Args[n++] = "--auto-deconfigure";
+ Size += strlen(Args[n-1]);
break;
}
it doesn't die but we do! So we must also ignore it */
sighandler_t old_SIGQUIT = signal(SIGQUIT,SIG_IGN);
sighandler_t old_SIGINT = signal(SIGINT,SIG_IGN);
-
- // Fork dpkg
+
+ struct termios tt;
+ struct winsize win;
+ int master;
+ int slave;
+
+ // FIXME: setup sensible signal handling (*ick*)
+ tcgetattr(0, &tt);
+ ioctl(0, TIOCGWINSZ, (char *)&win);
+ if (openpty(&master, &slave, NULL, &tt, &win) < 0)
+ {
+ fprintf(stderr, _("openpty failed\n"));
+ }
+
+ struct termios rtt;
+ rtt = tt;
+ cfmakeraw(&rtt);
+ rtt.c_lflag &= ~ECHO;
+ tcsetattr(0, TCSAFLUSH, &rtt);
+
+ // Fork dpkg
pid_t Child;
- if(status_fd > 0)
- Child = ExecFork(status_fd);
- else
- Child = ExecFork();
+ _config->Set("APT::Keep-Fds::",fd[1]);
+ Child = ExecFork();
// This is the child
if (Child == 0)
{
+ setsid();
+ ioctl(slave, TIOCSCTTY, 0);
+ close(master);
+ dup2(slave, 0);
+ dup2(slave, 1);
+ dup2(slave, 2);
+ close(slave);
+ close(fd[0]); // close the read end of the pipe
+
if (chdir(_config->FindDir("DPkg::Run-Directory","/").c_str()) != 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");
_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];
+ close(fd[1]); // close the write end of the pipe
+
+ // the read buffers for the communication with dpkg
+ char buf[2] = {0,0};
+
+ // the result of the waitpid call
+ int res;
+ close(slave);
+
+ // FIXME: make this a apt config option and add a logrotate file
+ FILE *term_out = fopen("/var/log/dpkg-out.log","a");
+ chmod("/var/log/dpkg-out.log", 0600);
+ // output current time
+ char outstr[200];
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+ strftime(outstr, sizeof(outstr), "%F %T", tmp);
+ fprintf(term_out, "Log started: ");
+ fprintf(term_out, outstr);
+ fprintf(term_out, "\n");
+
+ // setups fds
+ fd_set rfds;
+ struct timeval tv;
+ int select_ret;
+ 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");
+ }
+
+ // wait for input or output here
+ FD_ZERO(&rfds);
+ FD_SET(0, &rfds);
+ FD_SET(_dpkgin, &rfds);
+ FD_SET(master, &rfds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ select_ret = select(max(master, _dpkgin)+1, &rfds, NULL, NULL, &tv);
+ if (select_ret < 0)
+ std::cerr << "Error in select()" << std::endl;
+ else if (select_ret == 0)
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");
+ if(FD_ISSET(master, &rfds))
+ DoTerminalPty(master, term_out);
+ if(FD_ISSET(0, &rfds))
+ DoStdin(master);
+
+ if(FD_ISSET(_dpkgin, &rfds))
+ DoDpkgStatusFd(_dpkgin);
+#if 0
+ while(true)
+ {
+ if(read(_dpkgin, buf, 1) <= 0)
+ break;
+
+ // sanity check (should never happen)
+ if(strlen(line) >= sizeof(line)-10)
+ {
+ _error->Error("got a overlong line from dpkg: '%s'",line);
+ line[0]=0;
+ continue;
+ }
+ // 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: <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
+
+ */
+ 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<struct DpkgState> &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;
+ }
+#endif
}
+ close(_dpkgin);
+ fclose(term_out);
// Restore sig int/quit
signal(SIGQUIT,old_SIGQUIT);
signal(SIGINT,old_SIGINT);
+
+ tcsetattr(0, TCSAFLUSH, &tt);
// 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 received 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;
}
}