ede/elauncher/elauncher.cpp
2007-03-15 01:06:18 +00:00

757 lines
19 KiB
C++

/*
* $Id$
*
* Program and URL opener
* Provides startup notification, crash handler and other features
* Part of Equinox Desktop Environment (EDE).
* Copyright (c) 2000-2006 EDE Authors.
*
* This program is licenced under terms of the
* GNU General Public Licence version 2 or newer.
* See COPYING for details.
*/
#include "elauncher.h"
#include "../edeconf.h"
#include "../edelib2/process.h"
#include "../edelib2/MimeType.h"
using namespace fltk;
using namespace edelib;
// TODO: find where to replace magic constants with fltk::PATH_MAX
// globals used in forking
int fds_[3];
char *cmd_;
int pid_;
// command-line parameters
bool param_root = false;
bool param_secure = false;
bool param_term = false;
// from configuration file
bool use_sudo = false;
char *output;
/*char *
itoa(int value, char *string, int radix)
{
char tmp[33];
char *tp = tmp;
int i;
unsigned v;
int sign;
char *sp;
if (radix > 36 || radix <= 1)
{
return 0;
}
sign = (radix == 10 && value < 0);
if (sign)
v = -value;
else
v = (unsigned)value;
while (v || tp == tmp)
{
i = v % radix;
v = v / radix;
if (i < 10)
*tp++ = i+'0';
else
*tp++ = i + 'a' - 10;
}
if (string == 0)
string = (char *)malloc((tp-tmp)+sign+1);
sp = string;
if (sign)
*sp++ = '-';
while (tp > tmp)
*sp++ = *--tp;
*sp = 0;
return string;
}*/
// --------------------------------------------
// Show busy cursor - not working
// --------------------------------------------
void show_busy_screen(bool busy)
{
// We can't use fltk::Cursor cause it can be set only per widget...
// and only if you overload that widget!
// I hate OOP :)
::Cursor xcursor;
if (busy)
xcursor = XCreateFontCursor(xdisplay, XC_watch);
else
xcursor = XCreateFontCursor(xdisplay, XC_arrow);
// Hopefully this is desktop?
XDefineCursor(xdisplay, CreatedWindow::first->xid, xcursor);
sleep (3);
}
// --------------------------------------------
// Show a generic window for displaying output stream
// --------------------------------------------
void output_window_close(Widget *w)
{
w->window()->hide();
}
void output_window(char *title, char *content)
{
int height=0;
TextBuffer buffer;
buffer.text(content);
for (unsigned i=0;i<strlen(content);i++)
if (content[i] == '\n') height+=20;
height+=45;
if (height>550) height=550;
if (height<100) height=100;
Window window(500, height);
window.label(title);
window.begin();
TextDisplay message(0, 0, 500, height-23, content);
window.resizable(message);
message.color(WHITE);
message.textcolor(BLACK);
message.buffer(buffer);
Button* button;
button = new ReturnButton(410, height-23, 80, 23, _("&Ok"));
button->callback(output_window_close);
window.hotspot(button);
// window.focus(button);
window.end();
window.exec();
}
// --------------------------------------------
// Crash window - with details
// --------------------------------------------
#define GDBPATH "/usr/bin/gdb"
static xpmImage crash_pix((const char **)crash_xpm);
Window *crashWindow;
Button *crashDetailsButton, *crashCloseButton;
TextDisplay *backTraceTD;
TextBuffer *gdbbuffer;
void cb_crashDetails(Widget *w) {
if (backTraceTD->visible()) {
backTraceTD->hide();
crashWindow->resize(450,110);
} else {
crashWindow->resize(450,395);
backTraceTD->show();
}
}
void cb_crashOk(Widget *w) {
w->window()->hide();
}
// Execute gdb and place output into gdbbuffer
bool get_me_gdb_output(int crashpid)
{
int pid, status;
extern char **environ;
status=0;
pid = fork ();
if (pid == -1)
return false;
if (pid == 0)
{
// child
char *argv[4];
char tmp[1000];
argv[0] = "sh";
argv[1] = "-c";
argv[2] = tmp;
snprintf (argv[2], 999, "echo bt>/tmp/gdbbatch; echo q>>/tmp/gdbbatch; "GDBPATH" %s --core core.%d --command /tmp/gdbbatch --quiet > /tmp/gdboutput", cmd_, crashpid);
argv[3] = NULL;
if (execve ("/bin/sh", argv, environ) == -1)
perror ("/bin/sh");
return false; // Error
}
do
{
if (waitpid (pid, &status, 0) == -1)
{
if (errno != EINTR)
return false; // Error
}
else {
gdbbuffer->loadfile("/tmp/gdboutput");
// Take out the garbage
char *corefile = (char*)malloc(20);
snprintf (corefile, sizeof(corefile)-1, "./core.%d", crashpid);
unlink(corefile);
free(corefile);
return true;
}
}
while (1);
}
void crashmessage(char *command, int pid)
{
gdbbuffer = new TextBuffer;
Window* w;
{
Window* o = crashWindow = new Window(450, 110, _("The program has crashed"));
w = o;
o->shortcut(0xff1b);
o->begin();
{
Button* o = crashDetailsButton = new Button(250, 75, 90, 25, _("&Details..."));
o->callback((Callback*)cb_crashDetails);
o->type(Button::TOGGLE);
}
{
Button* o = crashCloseButton = new Button(350, 75, 90, 25, _("&Close"));
o->callback((Callback*)cb_crashOk);
}
{
InvisibleBox* o = new InvisibleBox(60, 5, 380, 16, _("An error occured in program:"));
o->align(ALIGN_LEFT|ALIGN_INSIDE);
}
{
InvisibleBox* o = new InvisibleBox(90, 20, 380, 16, command);
o->labelfont(o->labelfont()->bold());
o->align(ALIGN_LEFT|ALIGN_INSIDE);
}
{
InvisibleBox* o = new InvisibleBox(60, 35, 380, 30, _("Please inform the authors of this program and provide the details below."));
o->align(ALIGN_LEFT|ALIGN_INSIDE|ALIGN_WRAP);
}
{
InvisibleBox* o = new InvisibleBox(15, 15, 35, 35, "");
o->image(crash_pix);
}
{
TextDisplay* o = backTraceTD = new TextDisplay(10, 110, 430, 275, "");
o->hide();
o->color(WHITE);
o->textcolor(BLACK);
o->buffer(gdbbuffer);
}
o->end();
}
w->show();
flush();
// Is there gdb on the system?
struct stat *buf = (struct stat*)malloc(sizeof(struct stat));
if (stat (GDBPATH, buf) != 0 || !get_me_gdb_output(pid))
crashDetailsButton->deactivate();
w->exec();
return;
}
// --------------------------------------------
// Error message window
// --------------------------------------------
// This should be replaced with one of redesigned standard dialogs...
static xpmImage error_pix((const char **)error_xpm);
void cb_errOk(Widget *w) {
w->window()->hide();
}
void errormessage(char *part1, char *part2, char *part3)
{
Window* w;
{
Window* o = new Window(350, 100, _("Error"));
w = o;
o->shortcut(0xff1b);
o->begin();
{
ReturnButton* o = new ReturnButton(250, 65, 90, 25, _("&OK"));
o->callback((Callback*)cb_errOk);
}
{
InvisibleBox* o = new InvisibleBox(60, 5, 280, 16, part1);
o->align(ALIGN_LEFT|ALIGN_INSIDE);
}
{
InvisibleBox* o = new InvisibleBox(90, 20, 280, 16, part2);
o->labelfont(o->labelfont()->bold());
o->align(ALIGN_LEFT|ALIGN_INSIDE);
}
{
InvisibleBox* o = new InvisibleBox(60, 35, 280, 30, part3);
o->align(ALIGN_LEFT|ALIGN_INSIDE|ALIGN_WRAP);
}
{
InvisibleBox* o = new InvisibleBox(15, 15, 35, 35, "");
o->image(error_pix);
}
o->end();
}
w->exec();
return;
}
// --------------------------------------------
// Depending on exit status, show some nice dialogs
// --------------------------------------------
void process_output_status(int exit_status, PtyProcess* child)
{
char *messages1[257], *messages2[257];
// FIXME: do we still need this init?
for (int i=0;i<256;i++) { messages1[i] = ""; messages2[i] = ""; }
if (exit_status == PtyProcess::Killed) exit_status = 256;
messages1[127] = _("Program not found:");
messages2[127] = _("Perhaps it is not installed properly. Check your $PATH value.");
// messages1[14] = _("Segmentation fault in child process:");
// messages2[14] = _("");
messages1[126] = _("File is not executable:");
messages2[126] = _("Is this really a program? If it is, you should check its permissions.");
messages1[256] = _("Program was terminated:");
messages2[256] = _("");
if (exit_status == PtyProcess::Crashed) {
// Nice bomb window
crashmessage(cmd_,child->pid());
} else if (!(messages1[exit_status] == "")) {
// we have special message for this status
errormessage(messages1[exit_status],cmd_,messages2[exit_status]);
} else {
fprintf(stderr, _("Elauncher: child's exited normally with status %d\n"), exit_status);
if (exit_status>0) {
// unknown status, display stdout & stderr
char *buffer;
char output[65535];
bool changed=false;
strcpy(output,"");
while (buffer = child->readLine()) {
strcat(output, buffer);
changed=true;
}
if (changed) output_window(_("Program output"),output);
}
}
}
// --------------------------------------------
// Core function that handles su/sudo, waits for program to
// finish and then calls the output handler
// --------------------------------------------
// this is our internal message:
#define CONTMSG "elauncher_ok_to_continue"
// these are part of sudo/su chat:
#define PWDQ "Password:"
#define BADPWD "/bin/su: incorrect password"
#define SUDOBADPWD "Sorry, try again."
// We can't use const char* because of strtok later
int start_child_process(char *cmd)
{
if (strlen(cmd)<1) return 0;
// show_busy_screen(true);
// return 0;
// This is so that we can get a backtrace in case of crash
struct rlimit *rlim = (struct rlimit*)malloc(sizeof(struct rlimit));
getrlimit (RLIMIT_CORE, rlim);
rlim_t old_rlimit = rlim->rlim_cur; // keep previous rlimit
rlim->rlim_cur = RLIM_INFINITY;
setrlimit (RLIMIT_CORE, rlim);
// Prepare array as needed by exec()
char *parts[4];
if (param_root) {
if (use_sudo) {
parts[0] = "/bin/sudo";
parts[1] = "";
} else {
parts[0] = "/bin/su";
parts[1] = "-c";
}
// This "continue message" prevents accidentally exposing password
int length = strlen("echo "CONTMSG)+strlen("; ")+strlen(cmd);
parts[2] = (char*)malloc(length);
snprintf(parts[2],length,"echo %s; %s",CONTMSG,cmd);
parts[3] = NULL;
} else {
parts[0] = "/bin/sh";
parts[1] = "-c";
parts[2] = strdup(cmd);
parts[3] = NULL;
}
// the actual command is this:
cmd_ = strtok(cmd," ");
tryagain:
PtyProcess *child = new PtyProcess();
child->setEnvironment((const char**)environ);
if (child->exec(parts[0], (const char**)parts) < 0) {
if (ask(_("Error starting program. Try again?")))
goto tryagain;
else
return 0;
}
// Wait for process to actually start. Shouldn't last long
while (1) {
int p = child->pid();
if (p != 0 && child->checkPid(p))
break;
int exit = child->checkPidExited(p);
if (exit != -2) {
// Process is DOA
fprintf (stderr, "Elauncher: Process has died unexpectedly! Exit status: %d\n",exit);
delete child;
goto tryagain;
}
fprintf (stderr, "Elauncher: Not started yet...\n");
}
// Run program as root using su or sudo
if (param_root) {
char *line;
const char *pwd = password(_("This program requires administrator privileges.\nPlease enter your password below:"));
if (pwd == 0) { fprintf(stderr,"Canceled\n"); exit(1); }
// Chat routine
while (1) {
line = child->readLine();
// This covers other cases of failed process startup
// Our su command should at least produce CONTMSG
if (line == 0 && child->checkPidExited(child->pid()) != PtyProcess::NotExited) {
// TODO: capture stdout? as in sudo error?
fprintf (stderr, "Elauncher: su process has died unexpectedly in chat stage!\n");
delete child;
if (choice_alert (_("Failed to start authentication. Try again"), 0, _("Yes"), _("No")) == 2) return 0;
goto tryagain;
}
if (strncasecmp(line,PWDQ,strlen(PWDQ))== 0)
child->writeLine(pwd,true);
if (strncasecmp(line,CONTMSG,strlen(CONTMSG)) == 0)
break; // program starts...
if ((strncasecmp(line,BADPWD,strlen(BADPWD)) == 0) || (strncasecmp(line,SUDOBADPWD,strlen(SUDOBADPWD)) == 0)) {
// end process
child->waitForChild();
delete child;
if (choice_alert (_("The password is wrong. Try again?"), 0, _("Yes"), _("No")) == 2) return 0;
goto tryagain;
}
}
}
// Wait for program to end, but don't lose the output
// show_busy_screen(false);
// cursor(CURSOR_ARROW);
int child_val = child->runChild();
process_output_status(child_val,child);
// deallocate one string we mallocated
free(parts[2]);
delete child;
// Revert old rlimit
rlim->rlim_cur = old_rlimit;
setrlimit (RLIMIT_CORE, rlim);
return 0;
}
// --------------------------------------------
// Analyze command and, if it's URL, call appropriate application
// Otherwise assume that it's executable and run it
// (Code mostly copied over from former eRun)
// --------------------------------------------
void run_resource(const char *cmd) {
char pRun[256];
char browser[256];
// look up default browser in config
Config pGlobalConfig(Config::find_file("ede.conf", 0));
pGlobalConfig.get("Web", "Browser", browser, 0, sizeof(browser));
if(pGlobalConfig.error() && !browser) {
strncpy(browser, "mozilla", sizeof(browser));
}
// We might need this later, so try to optimize file reads
pGlobalConfig.get("System","UseSudo", use_sudo, false);
// split cmd to protocol and location
char *protocol = strdup(cmd);
char *location = strchr(protocol, ':');
if (location) *(location++) = '\0'; // cut off at ':'
// is cmd a proper URL?
if((location) && (strchr(protocol, ' ') == NULL))
{
if (strcasecmp(protocol,"file") == 0)
// use mimetypes
{
MimeType m(location);
if (m.command())
strncpy(pRun, m.command(), sizeof(pRun)-1);
else
{ // unknown extension
char m_printout[256];
snprintf(m_printout, sizeof(m_printout)-1, _("Unknown file type:\n\t%s\nTo open this file in 'appname' please use\n 'appname %s'"), location, location);
alert(m_printout);
return;
}
}
// TODO: split protocols into own file
else if (strcasecmp(protocol, "http")==0 || strcasecmp(protocol, "ftp")==0)
{
snprintf(pRun, sizeof(pRun)-1, "%s %s", browser, cmd);
}
// search engine urls
else if (strcasecmp(protocol, "gg")==0)
{
snprintf(pRun, sizeof(pRun)-1, "%s http://www.google.com/search?q=\"%s\"", browser, location);
}
else if (strcasecmp(protocol, "leo")==0)
{
snprintf(pRun, sizeof(pRun)-1, "%s http://dict.leo.org/?search=\"%s\"", browser, location);
}
else if (strcasecmp(protocol, "av")==0)
{
snprintf(pRun, sizeof(pRun)-1, "%s http://www.altavista.com/sites/search/web?q=\"%s\"", browser, location);
}
else // Unkown URL type - let browser deal with it
{
snprintf(pRun, sizeof(pRun)-1, "%s %s", browser, cmd);
}
}
else
// local executable
// TODO: parse the standard parameters to the executable if any exists in the *.desktop file.
{
if (param_secure) {
char message[256];
snprintf (message, sizeof(message)-1, _("You have requested to execute program %s via Elauncher. However, secure mode was enabled. Execution has been prevented."), cmd);
alert (message);
exit(1);
} else {
snprintf(pRun, sizeof(pRun)-1, "%s", cmd);
}
}
delete [] protocol;
// Additional parameters
if (param_term) {
char termapp[256];
pGlobalConfig.get("Terminal", "Terminal", termapp, 0, sizeof(termapp));
char tmp[256];
snprintf (tmp, sizeof(pRun)-1, "%s -e %s",termapp,pRun);
strcpy (pRun, tmp);
}
if (param_root) {
// nothing special to do here
}
// continue with execution
start_child_process(pRun);
}
// --------------------------------------------
// Draw GUI run dialog. This is shown when no parameters are given
// (Code mostly copied over from former eRun)
// --------------------------------------------
static xpmImage run_pix((const char **)run_xpm);
Window* windowRunDialog;
Input* inputRunDialog;
CheckButton* runAsRoot;
static void cb_OK(Button*, void*) {
param_root = runAsRoot->value();
windowRunDialog->hide();
flush(); // Window will not hide without this...
run_resource(inputRunDialog->value());
}
static void cb_Cancel(Button*, void*) {
windowRunDialog->hide();
}
static void cb_Browse(Button*, void*) {
const char *file_types = _("Executables (*.*), *, All files (*.*), *");
const char *fileName = file_chooser(_("File selection..."), "*.*", inputRunDialog->value()); // TODO: fix file filter when we get a new dialog
if (fileName)
{
inputRunDialog->value(fileName);
}
}
void run_dialog() {
Window* w = windowRunDialog = new Window(350, 175, _("Open..."));
w->when(WHEN_ENTER_KEY);
w->begin();
{ InvisibleBox* o = new InvisibleBox(5, 5, 55, 70);
o->image(run_pix);
o->align(ALIGN_CENTER|ALIGN_INSIDE); }
{ InvisibleBox* o = new InvisibleBox(60, 5, 285, 70, _("Type the location you want to open or the name of the program you want to run\
. (Possible prefixes are: http:, ftp:, gg:, av:, leo:)"));
o->align(132|ALIGN_INSIDE); }
{ Input* o = inputRunDialog = new Input(60, 80, 180, 25, _("Open:"));
o->align(132);
//o->when(WHEN_ENTER_KEY);
}
{ Button* o = new Button(250, 80, 90, 25, _("&Browse..."));
o->callback((Callback*)cb_Browse); }
{ CheckButton* o = runAsRoot = new CheckButton(60, 110, 90, 25, _("Run as &root"));
}
{ ReturnButton* o = new ReturnButton(150, 140, 90, 25, _("&OK"));
o->callback((Callback*)cb_OK);
o->shortcut(ReturnKey); // ENTER
}
{ Button* o = new Button(250, 140, 90, 25, _("&Cancel"));
o->callback((Callback*)cb_Cancel); }
// -- window manager should do this
// w->x(fltk::xvisual->w / 2 - (w->w()/2));
// w->y( (fltk::xvisual->h / 2) - (w->h()/2));
w->end();
w->show();
run();
}
// Show console help on parameters
void showHelp() {
printf ("ELauncher - ");
printf (_("program and URL opener for EDE.\n"));
printf ("Copyright (c) 2004,2005 EDE Authors\n");
printf (_("Licenced under terms of GNU General Public Licence v2.0 or newer.\n\n"));
printf (_("Usage:\n"));
printf ("\telauncher [OPTIONS] [URL]\n");
printf ("\telauncher [OPTIONS] [PROGRAM]\n\n");
printf ("elauncher URL -\n");
printf (_("\tParse URL in form protocol:address and open in appropriate program.\n\tURLs with protocol 'file' are opened based on their MIME type.\n"));
printf ("elauncher PROGRAM -\n");
printf (_("\tRun the program. If no path is given, look in $PATH. To give parameters\n\tto program, use quotes e.g.:\n"));
printf ("\t\telauncher --term \"rm -rf /\"\n\n");
printf (_("Options:\n"));
printf (" -h, --help\t- ");
printf (_("This help screen.\n"));
printf (" --root\t- ");
printf (_("Run as root. Dialog is opened to enter password.\n"));
printf (" --secure\t- ");
printf (_("Prevent running programs. Only URLs are allowed.\n"));
printf (" --term\t- ");
printf (_("Open in default terminal app.\n\n"));
}
// parse command line parameters
int main (int argc, char **argv) {
char url[255];
url[0] = '\0';
// fl_init_locale_support("elauncher", PREFIX"/share/locale");
for (int i=1; i<argc; i++) {
// params
if ((strcmp(argv[i],"--help") == 0) || (strcmp(argv[i],"-h") == 0)) {
showHelp();
exit(0);
}
else if (strcmp(argv[i],"--root") == 0) {
param_root = true;
}
else if (strcmp(argv[i],"--secure") == 0) {
param_secure = true;
}
else if (strcmp(argv[i],"--term") == 0) {
param_term = true;
}
// someone is trying to run elauncher with elauncher
else if (strcmp(argv[i],"elauncher") == 0 || strcmp(argv[i],PREFIX"/bin/elauncher") == 0) {
// ignore
}
// there shouldn't be multiple url
else if (url[0] == '\0') {
strcpy (url, argv[i]);
} else {
fprintf (stderr, _("Elauncher: Wrong number of parameters...\n"));
exit (1);
}
}
if (url[0] == '\0') {
run_dialog();
} else {
run_resource(url);
}
}