/* * Copyright © 2014 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Alexander Larsson */ #include "config.h" #include #include #include #include #include #include #include #include "flatpak-dbus.h" #include "flatpak-utils.h" static char *monitor_dir; static GHashTable *client_pid_data_hash = NULL; static GDBusConnection *session_bus = NULL; typedef struct { GPid pid; char *client; guint child_watch; } PidData; static void pid_data_free (PidData *data) { g_free (data->client); g_free (data); } static gboolean handle_request_monitor (FlatpakSessionHelper *object, GDBusMethodInvocation *invocation, gpointer user_data) { flatpak_session_helper_complete_request_monitor (object, invocation, monitor_dir); return TRUE; } static void child_watch_died (GPid pid, gint status, gpointer user_data) { PidData *pid_data = user_data; g_autoptr(GVariant) signal_variant = NULL; g_debug ("Client Pid %d died", pid_data->pid); signal_variant = g_variant_ref_sink (g_variant_new ("(uu)", pid, status)); g_dbus_connection_emit_signal (session_bus, pid_data->client, "/org/freedesktop/Flatpak/Development", "org.freedesktop.Flatpak.Development", "HostCommandExited", signal_variant, NULL); /* This frees the pid_data, so be careful */ g_hash_table_remove (client_pid_data_hash, GUINT_TO_POINTER(pid_data->pid)); } typedef struct { int from; int to; int final; } FdMapEntry; typedef struct { FdMapEntry *fd_map; int fd_map_len; gboolean set_tty; int tty; } ChildSetupData; static void child_setup_func (gpointer user_data) { ChildSetupData *data = (ChildSetupData *)user_data; FdMapEntry *fd_map = data->fd_map; sigset_t set; int i; /* Unblock all signals */ sigemptyset (&set); if (pthread_sigmask (SIG_SETMASK, &set, NULL) == -1) { g_error ("Failed to unblock signals when starting child"); return; } /* Reset the handlers for all signals to their defaults. */ for (i = 1; i < NSIG; i++) { if (i != SIGSTOP && i != SIGKILL) signal (i, SIG_DFL); } for (i = 0; i < data->fd_map_len; i++) { if (fd_map[i].from != fd_map[i].to) { dup2 (fd_map[i].from, fd_map[i].to); close (fd_map[i].from); } } /* Second pass in case we needed an inbetween fd value to avoid conflicts */ for (i = 0; i < data->fd_map_len; i++) { if (fd_map[i].to != fd_map[i].final) { dup2 (fd_map[i].to, fd_map[i].final); close (fd_map[i].to); } } /* We become our own session and process group, because it never makes sense to share the flatpak-session-helper dbus activated process group */ setsid (); setpgid (0, 0); if (data->set_tty) { /* data->tty is our from fd which is closed at this point. * so locate the destnation fd and use it for the ioctl. */ for (i = 0; i < data->fd_map_len; i++) { if (fd_map[i].from == data->tty) { if (ioctl (fd_map[i].final, TIOCSCTTY, 0) == -1) g_debug ("ioctl(%d, TIOCSCTTY, 0) failed: %s", fd_map[i].final, strerror (errno)); break; } } } } static gboolean handle_host_command (FlatpakDevelopment *object, GDBusMethodInvocation *invocation, const gchar *arg_cwd_path, const gchar *const *arg_argv, GVariant *arg_fds, GVariant *arg_envs, guint flags) { g_autoptr(GError) error = NULL; GDBusMessage *message = g_dbus_method_invocation_get_message (invocation); GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list (message); ChildSetupData child_setup_data = { NULL }; GPid pid; PidData *pid_data; gsize i, j, n_fds, n_envs; const gint *fds; g_autofree FdMapEntry *fd_map = NULL; gchar **env; gint32 max_fd; if (*arg_cwd_path == 0) arg_cwd_path = NULL; if (arg_argv == NULL || *arg_argv == NULL || *arg_argv[0] == 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No command given"); return TRUE; } g_debug ("Running host command %s", arg_argv[0]); n_fds = 0; fds = NULL; if (fd_list != NULL) { n_fds = g_variant_n_children (arg_fds); fds = g_unix_fd_list_peek_fds (fd_list, NULL); } fd_map = g_new0 (FdMapEntry, n_fds); child_setup_data.fd_map = fd_map; child_setup_data.fd_map_len = n_fds; max_fd = -1; for (i = 0; i < n_fds; i++) { gint32 handle, fd; g_variant_get_child (arg_fds, i, "{uh}", &fd, &handle); fd_map[i].to = fd; fd_map[i].from = fds[i]; fd_map[i].final = fd_map[i].to; /* If stdin/out/err is a tty we try to set it as the controlling tty for the app, this way we can use this to run in a terminal. */ if ((fd == 0 || fd == 1 || fd == 2) && !child_setup_data.set_tty && isatty (fds[i])) { child_setup_data.set_tty = TRUE; child_setup_data.tty = fds[i]; } max_fd = MAX (max_fd, fd_map[i].to); max_fd = MAX (max_fd, fd_map[i].from); } /* We make a second pass over the fds to find if any "to" fd index overlaps an already in use fd (i.e. one in the "from" category that are allocated randomly). If a fd overlaps "to" fd then its a caller issue and not our fault, so we ignore that. */ for (i = 0; i < n_fds; i++) { int to_fd = fd_map[i].to; gboolean conflict = FALSE; /* At this point we're fine with using "from" values for this value (because we handle to==from in the code), or values that are before "i" in the fd_map (because those will be closed at this point when dup:ing). However, we can't reuse a fd that is in "from" for j > i. */ for (j = i + 1; j < n_fds; j++) { int from_fd = fd_map[j].from; if (from_fd == to_fd) { conflict = TRUE; break; } } if (conflict) fd_map[i].to = ++max_fd; } if (flags & FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV) { char *empty[] = { NULL }; env = g_strdupv (empty); } else env = g_get_environ (); n_envs = g_variant_n_children (arg_envs); for (i = 0; i < n_envs; i++) { const char *var = NULL; const char *val = NULL; g_variant_get_child (arg_envs, i, "{&s&s}", &var, &val); env = g_environ_setenv (env, var, val, TRUE); } if (!g_spawn_async_with_pipes (arg_cwd_path, (char **)arg_argv, env, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, child_setup_func, &child_setup_data, &pid, NULL, NULL, NULL, &error)) { gint code = G_DBUS_ERROR_FAILED; if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_ACCES)) code = G_DBUS_ERROR_ACCESS_DENIED; else if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) code = G_DBUS_ERROR_FILE_NOT_FOUND; g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code, "Failed to start command: %s", error->message); return TRUE; } pid_data = g_new0 (PidData, 1); pid_data->pid = pid; pid_data->client = g_strdup (g_dbus_method_invocation_get_sender (invocation)); pid_data->child_watch = g_child_watch_add_full (G_PRIORITY_DEFAULT, pid, child_watch_died, pid_data, NULL); g_debug ("Client Pid is %d", pid_data->pid); g_hash_table_replace (client_pid_data_hash, GUINT_TO_POINTER(pid_data->pid), pid_data); flatpak_development_complete_host_command (object, invocation, pid_data->pid); return TRUE; } static gboolean handle_host_command_signal (FlatpakDevelopment *object, GDBusMethodInvocation *invocation, guint arg_pid, guint arg_signal, gboolean to_process_group) { PidData *pid_data = NULL; pid_data = g_hash_table_lookup (client_pid_data_hash, GUINT_TO_POINTER(arg_pid)); if (pid_data == NULL || strcmp (pid_data->client, g_dbus_method_invocation_get_sender (invocation)) != 0) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "No such pid"); return TRUE; } g_debug ("Sending signal %d to client pid %d", arg_signal, arg_pid); if (to_process_group) killpg (pid_data->pid, arg_signal); else kill (pid_data->pid, arg_signal); flatpak_development_complete_host_command_signal (object, invocation); return TRUE; } static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { FlatpakSessionHelper *helper; FlatpakDevelopment *devel; GError *error = NULL; helper = flatpak_session_helper_skeleton_new (); g_signal_connect (helper, "handle-request-monitor", G_CALLBACK (handle_request_monitor), NULL); if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (helper), connection, "/org/freedesktop/Flatpak/SessionHelper", &error)) { g_warning ("error: %s\n", error->message); g_error_free (error); } devel = flatpak_development_skeleton_new (); g_signal_connect (devel, "handle-host-command", G_CALLBACK (handle_host_command), NULL); g_signal_connect (devel, "handle-host-command-signal", G_CALLBACK (handle_host_command_signal), NULL); if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (devel), connection, "/org/freedesktop/Flatpak/Development", &error)) { g_warning ("error: %s\n", error->message); g_error_free (error); } } static void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { } static void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data) { exit (1); } static void copy_file (const char *source, const char *target_dir) { char *basename = g_path_get_basename (source); char *dest = g_build_filename (target_dir, basename, NULL); gchar *contents = NULL; gsize len; if (g_file_get_contents (source, &contents, &len, NULL)) g_file_set_contents (dest, contents, len, NULL); g_free (basename); g_free (dest); g_free (contents); } static void file_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, char *source) { if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT || event_type == G_FILE_MONITOR_EVENT_CREATED) copy_file (source, monitor_dir); } static void setup_file_monitor (const char *source) { GFile *s = g_file_new_for_path (source); GFileMonitor *monitor; copy_file (source, monitor_dir); monitor = g_file_monitor_file (s, G_FILE_MONITOR_NONE, NULL, NULL); if (monitor) g_signal_connect (monitor, "changed", G_CALLBACK (file_changed), (char *) source); } static void message_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { /* Make this look like normal console output */ if (log_level & G_LOG_LEVEL_DEBUG) g_printerr ("XA: %s\n", message); else g_printerr ("%s: %s\n", g_get_prgname (), message); } int main (int argc, char **argv) { guint owner_id; GMainLoop *loop; gboolean replace; gboolean verbose; gboolean show_version; GOptionContext *context; GBusNameOwnerFlags flags; g_autoptr(GError) error = NULL; const GOptionEntry options[] = { { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "Replace old daemon.", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Enable debug output.", NULL }, { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, "Show program version.", NULL}, { NULL } }; setlocale (LC_ALL, ""); g_setenv ("GIO_USE_VFS", "local", TRUE); g_set_prgname (argv[0]); g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, message_handler, NULL); context = g_option_context_new (""); replace = FALSE; verbose = FALSE; show_version = FALSE; g_option_context_set_summary (context, "Flatpak session helper"); g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_printerr ("%s: %s", g_get_application_name(), error->message); g_printerr ("\n"); g_printerr ("Try \"%s --help\" for more information.", g_get_prgname ()); g_printerr ("\n"); g_option_context_free (context); return 1; } if (show_version) { g_print (PACKAGE_STRING "\n"); return 0; } if (verbose) g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL); flatpak_migrate_from_xdg_app (); client_pid_data_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)pid_data_free); session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (session_bus == NULL) { g_printerr ("Can't find bus: %s\n", error->message); return 1; } monitor_dir = g_build_filename (g_get_user_runtime_dir (), "flatpak-monitor", NULL); if (g_mkdir_with_parents (monitor_dir, 0755) != 0) { g_print ("Can't create %s\n", monitor_dir); exit (1); } setup_file_monitor ("/etc/resolv.conf"); setup_file_monitor ("/etc/localtime"); flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; if (replace) flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, "org.freedesktop.Flatpak", flags, on_bus_acquired, on_name_acquired, on_name_lost, NULL, NULL); loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); g_bus_unown_name (owner_id); return 0; }