##################################################################### */
/*}}}*/
// 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 <sstream>
#include <map>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <pty.h>
+
#include <config.h>
#include <apti18n.h>
/*}}}*/
static const struct DpkgState DpkgStatesOpMap[][5] = {
// Install operation
{
- {"half-installed", _("Preparing %s")},
- {"unpacked", _("Unpacking %s") },
- NULL
+ {"half-installed", N_("Preparing %s")},
+ {"unpacked", N_("Unpacking %s") },
+ {NULL, NULL}
},
// Configure operation
{
- {"unpacked",_("Preparing to configure %s") },
- {"half-configured", _("Configuring %s") },
- { "installed", _("Installed %s")},
- NULL
+ {"unpacked",N_("Preparing to configure %s") },
+ {"half-configured", N_("Configuring %s") },
+ { "installed", N_("Installed %s")},
+ {NULL, NULL}
},
// Remove operation
{
- {"half-configured", _("Preparing for removal of %s")},
- {"half-installed", _("Removing %s")},
- {"config-files", _("Removed %s")},
- NULL
+ {"half-configured", N_("Preparing for removal of %s")},
+ {"half-installed", N_("Removing %s")},
+ {"config-files", N_("Removed %s")},
+ {NULL, NULL}
},
// Purge operation
{
- {"config-files", _("Preparing for remove with config %s")},
- {"not-installed", _("Removed with config %s")},
- NULL
+ {"config-files", N_("Preparing to completely remove %s")},
+ {"not-installed", N_("Completely removed %s")},
+ {NULL, NULL}
},
};
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);
-
+
+ struct termios tt;
+ struct winsize win;
+ int master;
+ int slave;
+
+ 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;
_config->Set("APT::Keep-Fds::",fd[1]);
// 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");
// the read buffers for the communication with dpkg
char line[1024] = {0,};
+
char buf[2] = {0,0};
+ char term_buf[2] = {0,0};
+ char input_buf[2] = {0,0};
// the result of the waitpid call
int res;
-
+ close(slave);
+ fcntl(0, F_SETFL, O_NONBLOCK);
+ fcntl(master, F_SETFL, O_NONBLOCK);
+ FILE *term_out = fopen("/var/log/dpkg-out.log","a");
+ chmod("/var/log/dpkg-out.log", 0600);
+
while ((res=waitpid(Child,&Status, WNOHANG)) != Child) {
if(res < 0) {
// FIXME: move this to a function or something, looks ugly here
signal(SIGINT,old_SIGINT);
return _error->Errno("waitpid","Couldn't wait for subprocess");
}
+
+ // wait for input or output here
+
+ // FIXME: use select() instead of the rubish below
// read a single char, make sure that the read can't block
// (otherwise we may leave zombies)
+ int term_len = read(master, term_buf, 1);
+ int input_len = read(0, input_buf, 1);
int len = read(_dpkgin, buf, 1);
- // nothing to read, wait a bit for more
+ // see if we have any input that needs to go to the
+ // master pty
+ if(input_len > 0)
+ write(master, input_buf, 1);
+
+ // see if we have any output that needs to be echoed
+ // and written to the log
+ if(term_len > 0)
+ {
+ do
+ {
+ fwrite(term_buf, 1, 1, term_out);
+ write(1, term_buf, 1);
+ } while(read(master, term_buf, 1) > 0);
+ term_buf[0] = 0;
+ }
+
+ // nothing to read from dpkg , wait a bit for more
if(len <= 0)
{
usleep(1000);
continue;
}
-
+
// sanity check (should never happen)
if(strlen(line) >= sizeof(line)-10)
{
if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
std::clog << "got from dpkg '" << line << "'" << std::endl;
- // pass "error" and "conffile-prompt" messages from dpkg verbatim
- if((strncmp(line,"error",strlen("error")) == 0) ||
- (strncmp(line,"conffile-prompt",strlen("conffile-prompt")) == 0))
- {
- write(OutStatusFd, line, strlen(line));
- line[0]=0;
- continue;
- }
- // line contains the dpkg status info now. it has the form:
- // 'status: <pkg>: <pkg qstate>' (see dpkg(1) for details)
+ // 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];
- TokSplitString(':', line, 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 = list[2];
+ 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 = states[PackageOpsDone[pkg]].state;
- const char *translation = states[PackageOpsDone[pkg]].str;
- char s[200];
- snprintf(s, sizeof(s), translation, 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++;
- ostringstream status;
// build the status str
status << "pmstatus:" << pkg
<< ":" << (Done/float(Total)*100.0)
if(OutStatusFd > 0)
write(OutStatusFd, status.str().c_str(), status.str().size());
if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
- {
- std::clog << "send to fd: '" << status.str()
- << "'" << std::endl;
-
- }
+ std::clog << "send: '" << status.str() << "'" << endl;
}
if (_config->FindB("Debug::pkgDPkgProgressReporting",false) == true)
line[0]=0;
}
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;
}
}