X-Git-Url: http://git.maemo.org/git/?p=browser-switch;a=blobdiff_plain;f=launcher.c;h=77f2ded16da9189e3c7d4c276e07bb4b1a29c0b5;hp=93cce2f4c6695caaa690f865dd0492b2a7a4fd4a;hb=1e3b1303d7f5740a01cc7386014c2984556124cb;hpb=17853b6cdd152a6c53a3e3e11506e3a780e4991c diff --git a/launcher.c b/launcher.c index 93cce2f..77f2ded 100644 --- a/launcher.c +++ b/launcher.c @@ -1,7 +1,7 @@ /* * launcher.c -- functions for launching web browsers for browser-switchboard * - * Copyright (C) 2009 Steven Luo + * Copyright (C) 2009-2010 Steven Luo * Derived from a Python implementation by Jason Simpson and Steven Luo * * This program is free software; you can redistribute it and/or @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -32,17 +33,24 @@ #ifdef FREMANTLE #include +#include +#include +#include + +#define DEFAULT_HOMEDIR "/home/user" +#define MICROB_PROFILE_DIR "/.mozilla/microb" +#define MICROB_LOCKFILE "lock" #endif #include "browser-switchboard.h" #include "launcher.h" #include "dbus-server-bindings.h" +#include "log.h" #define LAUNCH_DEFAULT_BROWSER launch_microb #ifdef FREMANTLE static int microb_started = 0; -static int kill_microb = 0; /* Check to see whether MicroB is ready to handle D-Bus requests yet See the comments in launch_microb to understand how this works. */ @@ -52,56 +60,41 @@ static DBusHandlerResult check_microb_started(DBusConnection *connection, DBusError error; char *name, *old, *new; - printf("Checking to see if MicroB is ready\n"); + log_msg("Checking to see if MicroB is ready\n"); dbus_error_init(&error); if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID)) { - printf("%s\n", error.message); + log_msg("%s\n", error.message); dbus_error_free(&error); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* If old is an empty string, then the name has been acquired, and MicroB should be ready to handle our request */ if (strlen(old) == 0) { - printf("MicroB ready\n"); + log_msg("MicroB ready\n"); microb_started = 1; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } -/* Check to see whether the last MicroB window has closed - See the comments in launch_microb to understand how this works. */ -static DBusHandlerResult check_microb_finished(DBusConnection *connection, - DBusMessage *message, - void *user_data) { - DBusError error; - char *name, *old, *new; +/* Get a browserd PID from the corresponding Mozilla profile lockfile */ +static pid_t get_browserd_pid(const char *lockfile) { + char buf[256], *tmp; - printf("Checking to see if we should kill MicroB\n"); - /* Check to make sure that the Mozilla.MicroB name is being released, - not acquired -- if it's being acquired, we might be seeing an event - at MicroB startup, in which case killing the browser isn't - appropriate */ - dbus_error_init(&error); - if (!dbus_message_get_args(message, &error, - DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &old, - DBUS_TYPE_STRING, &new, - DBUS_TYPE_INVALID)) { - printf("%s\n", error.message); - dbus_error_free(&error); - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; - } - /* If old isn't an empty string, the name is being released, and - we should now kill MicroB */ - if (strlen(old) > 0) - kill_microb = 1; + /* The lockfile is a symlink pointing to "[ipaddr]:+[pid]", so read in + the target of the symlink and parse it that way */ + memset(buf, '\0', 256); + if (readlink(lockfile, buf, 255) == -1) + return -errno; + if (!(tmp = strstr(buf, ":+"))) + return 0; + tmp += 2; /* Skip over the ":+" */ - return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + return atoi(tmp); } #endif @@ -128,7 +121,7 @@ static void launch_tear(struct swb_context *ctx, char *uri) { if (!uri) uri = "new_window"; - printf("launch_tear with uri '%s'\n", uri); + log_msg("launch_tear with uri '%s'\n", uri); /* We should be able to just call the D-Bus service to open Tear ... but if Tear's not open, that cuases D-Bus to start Tear and then @@ -138,19 +131,30 @@ static void launch_tear(struct swb_context *ctx, char *uri) { around by just invoking Tear with exec() if it's not running. */ status = system("pidof tear > /dev/null"); if (WIFEXITED(status) && !WEXITSTATUS(status)) { - if (!tear_proxy) - tear_proxy = dbus_g_proxy_new_for_name(ctx->session_bus, - "com.nokia.tear", "/com/nokia/tear", - "com.nokia.Tear"); - dbus_g_proxy_call(tear_proxy, "OpenAddress", &error, - G_TYPE_STRING, uri, G_TYPE_INVALID); + if (!tear_proxy) { + if (!(tear_proxy = dbus_g_proxy_new_for_name( + ctx->session_bus, + "com.nokia.tear", + "/com/nokia/tear", + "com.nokia.Tear"))) { + log_msg("Failed to create proxy for com.nokia.Tear D-Bus interface\n"); + exit(1); + } + } + + if (!dbus_g_proxy_call(tear_proxy, "OpenAddress", &error, + G_TYPE_STRING, uri, G_TYPE_INVALID, + G_TYPE_INVALID)) { + log_msg("Opening window failed: %s\n", error->message); + exit(1); + } if (!ctx->continuous_mode) exit(0); } else { if (ctx->continuous_mode) { if ((pid = fork()) != 0) { /* Parent process or error in fork() */ - printf("child: %d\n", (int)pid); + log_msg("child: %d\n", (int)pid); return; } /* Child process */ @@ -166,77 +170,122 @@ void launch_microb(struct swb_context *ctx, char *uri) { int status; pid_t pid; #ifdef FREMANTLE + char *homedir, *microb_profile_dir, *microb_lockfile; + size_t len; + int fd, inot_wd; DBusConnection *raw_connection; DBusError dbus_error; DBusHandleMessageFunction filter_func; DBusGProxy *g_proxy; GError *gerror = NULL; + int bytes_read; + char buf[256], *pos; + struct inotify_event *event; + pid_t browserd_pid, waited_pid; + struct sigaction act, oldact; + int ignore_sigstop; #endif if (!uri) uri = "new_window"; - printf("launch_microb with uri '%s'\n", uri); + log_msg("launch_microb with uri '%s'\n", uri); /* Launch browserd if it's not running */ - status = system("pidof /usr/sbin/browserd > /dev/null"); + status = system("pidof browserd > /dev/null"); if (WIFEXITED(status) && WEXITSTATUS(status)) { kill_browserd = 1; #ifdef FREMANTLE - system("/usr/sbin/browserd -d -b"); + system("/usr/sbin/browserd -d -b > /dev/null 2>&1"); #else - system("/usr/sbin/browserd -d"); + system("/usr/sbin/browserd -d > /dev/null 2>&1"); #endif } /* Release the osso_browser D-Bus name so that MicroB can take it */ dbus_release_osso_browser_name(ctx); +#ifdef FREMANTLE + /* Put together the path to the MicroB browserd lockfile */ + if (!(homedir = getenv("HOME"))) + homedir = DEFAULT_HOMEDIR; + len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) + 1; + if (!(microb_profile_dir = calloc(len, sizeof(char)))) { + log_msg("calloc() failed\n"); + exit(1); + } + snprintf(microb_profile_dir, len, "%s%s", + homedir, MICROB_PROFILE_DIR); + len = strlen(homedir) + strlen(MICROB_PROFILE_DIR) + + strlen("/") + strlen(MICROB_LOCKFILE) + 1; + if (!(microb_lockfile = calloc(len, sizeof(char)))) { + log_msg("calloc() failed\n"); + exit(1); + } + snprintf(microb_lockfile, len, "%s%s/%s", + homedir, MICROB_PROFILE_DIR, MICROB_LOCKFILE); + + /* Watch for the creation of a MicroB browserd lockfile + NB: The watch has to be set up here, before the browser + is launched, to make sure there's no race between browserd + starting and us creating the watch */ + if ((fd = inotify_init()) == -1) { + log_perror(errno, "inotify_init"); + exit(1); + } + if ((inot_wd = inotify_add_watch(fd, microb_profile_dir, + IN_CREATE)) == -1) { + log_perror(errno, "inotify_add_watch"); + exit(1); + } + free(microb_profile_dir); + + /* Set up the D-Bus eavesdropping we'll use to watch for MicroB + acquiring the com.nokia.osso_browser D-Bus name. Again, this needs + to happen before the browser is launched, so that there's no race + between establishing the watch and browser startup. + + Ideas for how to do this monitoring derived from the dbus-monitor + code (tools/dbus-monitor.c in the D-Bus codebase). */ + dbus_error_init(&dbus_error); + + raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION, &dbus_error); + if (!raw_connection) { + log_msg("Failed to open connection to session bus: %s\n", + dbus_error.message); + dbus_error_free(&dbus_error); + exit(1); + } + + dbus_bus_add_match(raw_connection, + "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'", + &dbus_error); + if (dbus_error_is_set(&dbus_error)) { + log_msg("Failed to set up watch for browser UI start: %s\n", + dbus_error.message); + dbus_error_free(&dbus_error); + exit(1); + } + filter_func = check_microb_started; + if (!dbus_connection_add_filter(raw_connection, + filter_func, NULL, NULL)) { + log_msg("Failed to set up watch filter!\n"); + exit(1); + } + if ((pid = fork()) == -1) { - perror("fork"); + log_perror(errno, "fork"); exit(1); } -#ifdef FREMANTLE + if (pid > 0) { /* Parent process */ /* Wait for our child to start the browser UI process and for it to acquire the com.nokia.osso_browser D-Bus name, then make the appropriate method call to open the browser - window. - - Ideas for how to do this monitoring derived from the - dbus-monitor code (tools/dbus-monitor.c in the D-Bus - codebase). */ + window. */ microb_started = 0; - dbus_error_init(&dbus_error); - - raw_connection = dbus_bus_get_private(DBUS_BUS_SESSION, - &dbus_error); - if (!raw_connection) { - fprintf(stderr, - "Failed to open connection to session bus: %s\n", - dbus_error.message); - dbus_error_free(&dbus_error); - exit(1); - } - - dbus_bus_add_match(raw_connection, - "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'", - &dbus_error); - if (dbus_error_is_set(&dbus_error)) { - fprintf(stderr, - "Failed to set up watch for browser UI start: %s\n", - dbus_error.message); - dbus_error_free(&dbus_error); - exit(1); - } - filter_func = check_microb_started; - if (!dbus_connection_add_filter(raw_connection, - filter_func, NULL, NULL)) { - fprintf(stderr, "Failed to set up watch filter!\n"); - exit(1); - } - printf("Waiting for MicroB to start\n"); + log_msg("Waiting for MicroB to start\n"); while (!microb_started && dbus_connection_read_write_dispatch(raw_connection, -1)); @@ -245,13 +294,12 @@ void launch_microb(struct swb_context *ctx, char *uri) { dbus_bus_remove_match(raw_connection, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.osso_browser'", &dbus_error); - if (dbus_error_is_set(&dbus_error)) { - fprintf(stderr, - "Failed to remove watch for browser UI start: %s\n", - dbus_error.message); + if (dbus_error_is_set(&dbus_error)) + /* Don't really care -- about to disconnect from the + bus anyhow */ dbus_error_free(&dbus_error); - exit(1); - } + dbus_connection_close(raw_connection); + dbus_connection_unref(raw_connection); /* Browser UI's started, send it the request for a new window via D-Bus */ @@ -260,7 +308,7 @@ void launch_microb(struct swb_context *ctx, char *uri) { "/com/nokia/osso_browser/request", "com.nokia.osso_browser"); if (!g_proxy) { - printf("Couldn't get a com.nokia.osso_browser proxy\n"); + log_msg("Couldn't get a com.nokia.osso_browser proxy\n"); exit(1); } if (!strcmp(uri, "new_window")) { @@ -271,8 +319,8 @@ void launch_microb(struct swb_context *ctx, char *uri) { if (!dbus_g_proxy_call(g_proxy, "top_application", &gerror, G_TYPE_INVALID, G_TYPE_INVALID)) { - printf("Opening window failed: %s\n", - gerror->message); + log_msg("Opening window failed: %s\n", + gerror->message); exit(1); } #endif @@ -281,8 +329,8 @@ void launch_microb(struct swb_context *ctx, char *uri) { G_TYPE_STRING, "about:blank", G_TYPE_INVALID, G_TYPE_INVALID)) { - printf("Opening window failed: %s\n", - gerror->message); + log_msg("Opening window failed: %s\n", + gerror->message); exit(1); } } else { @@ -291,90 +339,139 @@ void launch_microb(struct swb_context *ctx, char *uri) { G_TYPE_STRING, uri, G_TYPE_INVALID, G_TYPE_INVALID)) { - printf("Opening window failed: %s\n", - gerror->message); + log_msg("Opening window failed: %s\n", + gerror->message); exit(1); } } + g_object_unref(g_proxy); /* Workaround: the browser process we started is going to want to hang around forever, hogging the com.nokia.osso_browser D-Bus interface while at it. To fix this, we notice that when the last browser window closes, the browser UI restarts - its attached browserd process, which causes an observable - change in the ownership of the Mozilla.MicroB D-Bus name. - Watch for this change and kill off the browser UI process - when it happens. + its attached browserd process. Get the browserd process's + PID and use ptrace() to watch for process termination. This has the problem of not being able to detect whether the bookmark window is open and/or in use, but it's the best that I can think of. Better suggestions would be greatly appreciated. */ - kill_microb = 0; - dbus_bus_add_match(raw_connection, - "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'", - &dbus_error); - if (dbus_error_is_set(&dbus_error)) { - fprintf(stderr, - "Failed to set up watch for browserd restart: %s\n", - dbus_error.message); - dbus_error_free(&dbus_error); + + /* Wait for the MicroB browserd lockfile to be created */ + log_msg("Waiting for browserd lockfile to be created\n"); + memset(buf, '\0', 256); + /* read() blocks until there are events to be read */ + while ((bytes_read = read(fd, buf, 255)) > 0) { + pos = buf; + /* Loop until we see the event we're looking for + or until all the events are processed */ + while (pos && (pos-buf) < bytes_read) { + event = (struct inotify_event *)pos; + len = sizeof(struct inotify_event) + + event->len; + if (!strcmp(MICROB_LOCKFILE, + event->name)) { + /* Lockfile created */ + pos = NULL; + break; + } else if ((pos-buf) + len < bytes_read) + /* More events to process */ + pos += len; + else + /* All events processed */ + break; + } + if (!pos) + /* Event found, stop looking */ + break; + memset(buf, '\0', 256); + } + inotify_rm_watch(fd, inot_wd); + close(fd); + + /* Get the PID of the browserd from the lockfile */ + if ((browserd_pid = get_browserd_pid(microb_lockfile)) <= 0) { + if (browserd_pid == 0) + log_msg("Profile lockfile link lacks PID\n"); + else + log_perror(-browserd_pid, + "readlink() on lockfile failed"); exit(1); } - /* Maemo 5 PR1.1 seems to have changed the name browserd takes - to com.nokia.microb-engine; look for this too */ - dbus_bus_add_match(raw_connection, - "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'", - &dbus_error); - if (dbus_error_is_set(&dbus_error)) { - fprintf(stderr, - "Failed to set up watch for browserd restart: %s\n", - dbus_error.message); - dbus_error_free(&dbus_error); + free(microb_lockfile); + + /* Wait for the browserd to close */ + log_msg("Waiting for MicroB (browserd pid %d) to finish\n", + browserd_pid); + /* Clear any existing SIGCHLD handler to prevent interference + with our wait() */ + act.sa_handler = SIG_DFL; + act.sa_flags = 0; + sigemptyset(&(act.sa_mask)); + if (sigaction(SIGCHLD, &act, &oldact) == -1) { + log_perror(errno, "clearing SIGCHLD handler failed"); exit(1); } - filter_func = check_microb_finished; - if (!dbus_connection_add_filter(raw_connection, - filter_func, NULL, NULL)) { - fprintf(stderr, "Failed to set up watch filter!\n"); + + /* Trace the browserd to get a close notification */ + ignore_sigstop = 1; + if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) { + log_perror(errno, "PTRACE_ATTACH"); exit(1); } - while (!kill_microb && - dbus_connection_read_write_dispatch(raw_connection, - -1)); - dbus_connection_remove_filter(raw_connection, - filter_func, NULL); - dbus_bus_remove_match(raw_connection, - "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='Mozilla.MicroB'", - &dbus_error); - if (dbus_error_is_set(&dbus_error)) - /* Don't really care -- about to disconnect from the - bus anyhow */ - dbus_error_free(&dbus_error); - dbus_bus_remove_match(raw_connection, - "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='com.nokia.microb-engine'", - &dbus_error); - if (dbus_error_is_set(&dbus_error)) - dbus_error_free(&dbus_error); - dbus_connection_close(raw_connection); - dbus_connection_unref(raw_connection); - - /* Tell browser UI to exit nicely */ - printf("Closing MicroB\n"); - if (!dbus_g_proxy_call(g_proxy, "exit_browser", &gerror, - G_TYPE_INVALID, G_TYPE_INVALID)) { - /* We don't expect a reply; any other error indicates - a problem */ - if (gerror->domain != DBUS_GERROR || - gerror->code != DBUS_GERROR_NO_REPLY) { - printf("exit_browser failed: %s\n", - gerror->message); - exit(1); + ptrace(PTRACE_CONT, browserd_pid, NULL, NULL); + while ((waited_pid = wait(&status)) > 0) { + if (waited_pid != browserd_pid) + /* Not interested in other processes */ + continue; + if (WIFEXITED(status) || WIFSIGNALED(status)) + /* browserd exited */ + break; + else if (WIFSTOPPED(status)) { + /* browserd was sent a signal + We're responsible for making sure this + signal gets delivered */ + if (ignore_sigstop && + WSTOPSIG(status) == SIGSTOP) { + /* Ignore the first SIGSTOP received + This is raised for some reason + immediately after we start tracing + the process, and won't be followed + by a SIGCONT at any point */ + log_msg("Ignoring first SIGSTOP\n"); + ptrace(PTRACE_CONT, browserd_pid, + NULL, NULL); + ignore_sigstop = 0; + continue; + } + log_msg("Forwarding signal %d to browserd\n", + WSTOPSIG(status)); + ptrace(PTRACE_CONT, browserd_pid, + NULL, WSTOPSIG(status)); } } - g_object_unref(g_proxy); + + /* Kill off browser UI + XXX: There is a race here with the restarting of the closed + browserd; if that happens before we kill the browser UI, the + newly started browserd may not close with the UI + XXX: Hope we don't cause data loss here! */ + log_msg("Killing MicroB\n"); + kill(pid, SIGTERM); + waitpid(pid, &status, 0); + + /* Restore old SIGCHLD handler */ + if (sigaction(SIGCHLD, &oldact, NULL) == -1) { + log_perror(errno, + "restoring old SIGCHLD handler failed"); + exit(1); + } } else { /* Child process */ + dbus_connection_close(raw_connection); + dbus_connection_unref(raw_connection); + close(fd); close_stdio(); /* exec maemo-invoker directly instead of relying on the @@ -386,6 +483,11 @@ void launch_microb(struct swb_context *ctx, char *uri) { execl("/usr/bin/maemo-invoker", "browser", (char *)NULL); } #else /* !FREMANTLE */ + if ((pid = fork()) == -1) { + log_perror(errno, "fork"); + exit(1); + } + if (pid > 0) { /* Parent process */ waitpid(pid, &status, 0); @@ -408,7 +510,7 @@ void launch_microb(struct swb_context *ctx, char *uri) { /* Kill off browserd if we started it */ if (kill_browserd) - system("kill `pidof /usr/sbin/browserd`"); + system("kill `pidof browserd`"); if (!ctx || !ctx->continuous_mode) exit(0); @@ -427,7 +529,7 @@ static void launch_other_browser(struct swb_context *ctx, char *uri) { if (!uri || !strcmp(uri, "new_window")) uri = ""; - printf("launch_other_browser with uri '%s'\n", uri); + log_msg("launch_other_browser with uri '%s'\n", uri); if ((urilen = strlen(uri)) > 0) { /* Quote the URI to prevent the shell from interpreting it */ @@ -476,7 +578,7 @@ static void launch_other_browser(struct swb_context *ctx, char *uri) { if (!(command = calloc(cmdlen+urilen+1, sizeof(char)))) exit(1); snprintf(command, cmdlen+urilen+1, ctx->other_browser_cmd, quoted_uri); - printf("command: '%s'\n", command); + log_msg("command: '%s'\n", command); if (ctx->continuous_mode) { if (fork() != 0) { @@ -503,7 +605,7 @@ static void use_other_browser_cmd(struct swb_context *ctx, char *cmd) { free(ctx->other_browser_cmd); ctx->other_browser_cmd = calloc(len+1, sizeof(char)); if (!ctx->other_browser_cmd) { - printf("malloc failed!\n"); + log_msg("malloc failed!\n"); ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER; } else { ctx->other_browser_cmd = strncpy(ctx->other_browser_cmd, @@ -536,11 +638,12 @@ void update_default_browser(struct swb_context *ctx, char *default_browser) { if (ctx->other_browser_cmd) ctx->default_browser_launcher = launch_other_browser; else { - printf("default_browser is 'other', but no other_browser_cmd set -- using default\n"); + log_msg("default_browser is 'other', but no other_browser_cmd set -- using default\n"); ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER; } } else { - printf("Unknown default_browser %s, using default", default_browser); + log_msg("Unknown default_browser %s, using default", + default_browser); ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER; } }