X-Git-Url: http://git.maemo.org/git/?p=browser-switch;a=blobdiff_plain;f=launcher.c;h=10ff41b96c009ccc038542954ea4c389f4a3bcca;hp=6f25a28c0a783716c82c819cab279cfcf1e49b68;hb=6d9c8e22046fee196972730b0a41facbb20ee346;hpb=5a19c8657cd5a93c80f23d952e411e20517edd42 diff --git a/launcher.c b/launcher.c index 6f25a28..10ff41b 100644 --- a/launcher.c +++ b/launcher.c @@ -26,14 +26,90 @@ #include #include #include +#include +#include #include +#ifdef FREMANTLE +#include +#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" -#define DEFAULT_BROWSER "/usr/bin/tear" -#define LAUNCH_DEFAULT_BROWSER launch_tear +#define LAUNCH_DEFAULT_BROWSER launch_microb + +#ifdef FREMANTLE +static int microb_started = 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. */ +static DBusHandlerResult check_microb_started(DBusConnection *connection, + DBusMessage *message, + void *user_data) { + DBusError error; + char *name, *old, *new; + + printf("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); + 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"); + microb_started = 1; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +/* Get a browserd PID from the corresponding Mozilla profile lockfile */ +static pid_t get_browserd_pid(const char *lockfile) { + char buf[256], *tmp; + + /* 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 atoi(tmp); +} +#endif + +/* Close stdin/stdout/stderr and replace with /dev/null */ +static int close_stdio(void) { + int fd; + + if ((fd = open("/dev/null", O_RDWR)) == -1) + return -1; + + if (dup2(fd, 0) == -1 || dup2(fd, 1) == -1 || dup2(fd, 2) == -1) + return -1; + + close(fd); + return 0; +} static void launch_tear(struct swb_context *ctx, char *uri) { int status; @@ -47,8 +123,8 @@ static void launch_tear(struct swb_context *ctx, char *uri) { printf("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 star Tear and then pass - it the OpenAddress call, which results in two browser windows. + but if Tear's not open, that cuases D-Bus to start Tear and then + pass it the OpenAddress call, which results in two browser windows. Properly fixing this probably requires Tear to provide a D-Bus method that opens an address in an existing window, but for now work around by just invoking Tear with exec() if it's not running. */ @@ -71,6 +147,7 @@ static void launch_tear(struct swb_context *ctx, char *uri) { } /* Child process */ setsid(); + close_stdio(); } execl("/usr/bin/tear", "/usr/bin/tear", uri, (char *)NULL); } @@ -80,6 +157,22 @@ void launch_microb(struct swb_context *ctx, char *uri) { int kill_browserd = 0; 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"; @@ -87,24 +180,305 @@ void launch_microb(struct swb_context *ctx, char *uri) { printf("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; - system("/usr/sbin/browserd -d"); +#ifdef FREMANTLE + system("/usr/sbin/browserd -d -b > /dev/null 2>&1"); +#else + 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)))) { + printf("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)))) { + printf("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) { + perror("inotify_init"); + exit(1); + } + if ((inot_wd = inotify_add_watch(fd, microb_profile_dir, + IN_CREATE)) == -1) { + perror("inotify_add_watch"); + exit(1); + } + free(microb_profile_dir); + if ((pid = fork()) == -1) { perror("fork"); exit(1); } + 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). */ + 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"); + while (!microb_started && + 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='com.nokia.osso_browser'", + &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_connection_close(raw_connection); + dbus_connection_unref(raw_connection); + + /* Browser UI's started, send it the request for a new window + via D-Bus */ + g_proxy = dbus_g_proxy_new_for_name(ctx->session_bus, + "com.nokia.osso_browser", + "/com/nokia/osso_browser/request", + "com.nokia.osso_browser"); + if (!g_proxy) { + printf("Couldn't get a com.nokia.osso_browser proxy\n"); + exit(1); + } + if (!strcmp(uri, "new_window")) { +#if 0 /* Since we can't detect when the bookmark window closes, we'd have a + corner case where, if the user just closes the bookmark window + without opening any browser windows, we don't kill off MicroB or + resume handling com.nokia.osso_browser */ + if (!dbus_g_proxy_call(g_proxy, "top_application", + &gerror, G_TYPE_INVALID, + G_TYPE_INVALID)) { + printf("Opening window failed: %s\n", + gerror->message); + exit(1); + } +#endif + if (!dbus_g_proxy_call(g_proxy, "load_url", + &gerror, + G_TYPE_STRING, "about:blank", + G_TYPE_INVALID, + G_TYPE_INVALID)) { + printf("Opening window failed: %s\n", + gerror->message); + exit(1); + } + } else { + if (!dbus_g_proxy_call(g_proxy, "load_url", + &gerror, + G_TYPE_STRING, uri, + G_TYPE_INVALID, + G_TYPE_INVALID)) { + printf("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. 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. */ + + /* Wait for the MicroB browserd lockfile to be created */ + printf("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 */ + pos = buf + bytes_read; + } + 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) + printf("Profile lockfile link lacks PID\n"); + else + printf("readlink() on lockfile failed: %s\n", + strerror(-browserd_pid)); + exit(1); + } + free(microb_lockfile); + + /* Wait for the browserd to close */ + printf("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) { + perror("clearing SIGCHLD handler failed"); + exit(1); + } + + /* Trace the browserd to get a close notification */ + ignore_sigstop = 1; + if (ptrace(PTRACE_ATTACH, browserd_pid, NULL, NULL) == -1) { + perror("PTRACE_ATTACH"); + 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 */ + printf("Ignoring first SIGSTOP\n"); + ptrace(PTRACE_CONT, browserd_pid, + NULL, NULL); + ignore_sigstop = 0; + continue; + } + printf("Forwarding signal %d to browserd\n", + WSTOPSIG(status)); + ptrace(PTRACE_CONT, browserd_pid, + NULL, WSTOPSIG(status)); + } + } + + /* 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! */ + printf("Killing MicroB\n"); + kill(pid, SIGTERM); waitpid(pid, &status, 0); + + /* Restore old SIGCHLD handler */ + if (sigaction(SIGCHLD, &oldact, NULL) == -1) { + perror("restoring old SIGCHLD handler failed"); + exit(1); + } } else { /* Child process */ + close(fd); + close_stdio(); + + /* exec maemo-invoker directly instead of relying on the + /usr/bin/browser symlink, since /usr/bin/browser may have + been replaced with a shell script calling us via D-Bus */ + /* Launch the browser in the background -- our parent will + wait for it to claim the D-Bus name and then display the + window using D-Bus */ + execl("/usr/bin/maemo-invoker", "browser", (char *)NULL); + } +#else /* !FREMANTLE */ + if ((pid = fork()) == -1) { + perror("fork"); + exit(1); + } + + if (pid > 0) { + /* Parent process */ + waitpid(pid, &status, 0); + } else { + /* Child process */ + close_stdio(); + /* exec maemo-invoker directly instead of relying on the /usr/bin/browser symlink, since /usr/bin/browser may have been replaced with a shell script calling us via D-Bus */ @@ -116,10 +490,11 @@ void launch_microb(struct swb_context *ctx, char *uri) { "browser", "--url", uri, (char *)NULL); } } +#endif /* FREMANTLE */ /* 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); @@ -199,6 +574,7 @@ static void launch_other_browser(struct swb_context *ctx, char *uri) { } /* Child process */ setsid(); + close_stdio(); } execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL); } @@ -227,12 +603,8 @@ void update_default_browser(struct swb_context *ctx, char *default_browser) { return; if (!default_browser) { - /* No default_browser configured -- use DEFAULT_BROWSER if - installed, otherwise launch MicroB */ - if (!access(DEFAULT_BROWSER, X_OK)) - ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER; - else - ctx->default_browser_launcher = launch_microb; + /* No default_browser configured -- use built-in default */ + ctx->default_browser_launcher = LAUNCH_DEFAULT_BROWSER; return; }