/* sysprofd.c * * Copyright (C) 2016 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "sd-bus-helper.h" #define BUS_TIMEOUT_USEC (1000000L * 10L) static int _perf_event_open (struct perf_event_attr *attr, pid_t pid, int cpu, int group_fd, unsigned long flags) { assert (attr != NULL); /* Quick sanity check */ if (attr->sample_period < 100000) return -EINVAL; return syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags); } #if 0 #define GOTO(l) do { \ fprintf (stderr, "GOTO: %s:%d: " #l "\n", __FUNCTION__, __LINE__); \ goto l; \ } while (0) #else #define GOTO(l) goto l #endif static int sysprofd_perf_event_open (sd_bus_message *msg, void *user_data, sd_bus_error *error) { struct perf_event_attr attr = { 0 }; sd_bus_message *reply = NULL; sd_bus_message *kvpair; uint64_t flags = 0; int disabled = 0; int32_t wakeup_events = 149; int32_t cpu = -1; int32_t pid = -1; bool challenge = false; int32_t type = 0; uint64_t sample_period = 0; uint64_t sample_type = 0; uint64_t config = 0; int clockid = CLOCK_MONOTONIC_RAW; int comm = 0; int mmap_ = 0; int task = 0; int exclude_idle = 0; int fd = -1; int use_clockid = 0; int r; assert (msg); r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_ARRAY, "{sv}"); if (r < 0) return r; for (;;) { const char *name = NULL; r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_DICT_ENTRY, "sv"); if (r < 0) return r; if (r == 0) break; r = sd_bus_message_read (msg, "s", &name); if (r < 0) goto cleanup; r = sd_bus_message_enter_container (msg, SD_BUS_TYPE_VARIANT, NULL); if (r < 0) goto cleanup; if (strcmp (name, "disabled") == 0) { r = sd_bus_message_read (msg, "b", &disabled); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "wakeup_events") == 0) { r = sd_bus_message_read (msg, "u", &wakeup_events); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "clockid") == 0) { r = sd_bus_message_read (msg, "i", &clockid); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "comm") == 0) { r = sd_bus_message_read (msg, "b", &comm); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "exclude_idle") == 0) { r = sd_bus_message_read (msg, "b", &exclude_idle); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "mmap") == 0) { r = sd_bus_message_read (msg, "b", &mmap_); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "config") == 0) { r = sd_bus_message_read (msg, "t", &config); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "sample_period") == 0) { r = sd_bus_message_read (msg, "t", &sample_period); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "sample_type") == 0) { r = sd_bus_message_read (msg, "t", &sample_type); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "task") == 0) { r = sd_bus_message_read (msg, "b", &task); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "type") == 0) { r = sd_bus_message_read (msg, "u", &type); if (r < 0) GOTO (cleanup); } else if (strcmp (name, "use_clockid") == 0) { r = sd_bus_message_read (msg, "b", &use_clockid); if (r < 0) GOTO (cleanup); } r = sd_bus_message_exit_container (msg); if (r < 0) goto cleanup; sd_bus_message_exit_container (msg); if (r < 0) goto cleanup; cleanup: if (r < 0) return r; } r = sd_bus_message_exit_container (msg); if (r < 0) return r; r = sd_bus_message_read (msg, "iit", &pid, &cpu, &flags); if (r < 0) return r; if (pid < -1 || cpu < -1) return -EINVAL; r = sd_bus_message_new_method_return (msg, &reply); if (r < 0) return r; /* Authorize peer */ r = bus_test_polkit (msg, CAP_SYS_ADMIN, "org.gnome.sysprof2.perf-event-open", NULL, UID_INVALID, &challenge, error); if (r < 0) return r; else if (r == 0) return -EACCES; if (!use_clockid || clockid < 0) clockid = CLOCK_MONOTONIC_RAW; attr.comm = !!comm; attr.config = config; attr.disabled = disabled; attr.exclude_idle = !!exclude_idle; attr.mmap = !!mmap_; attr.sample_period = sample_period; attr.sample_type = sample_type; attr.size = sizeof attr; attr.task = !!task; attr.type = type; attr.clockid = clockid; attr.use_clockid = use_clockid; attr.wakeup_events = wakeup_events; fd = _perf_event_open (&attr, pid, cpu, -1, 0); if (fd < 0) { fprintf (stderr, "Failed to open perf event stream: %s\n", strerror (errno)); return -EINVAL; } sd_bus_message_append_basic (reply, SD_BUS_TYPE_UNIX_FD, &fd); r = sd_bus_send (NULL, reply, NULL); sd_bus_message_unref (reply); close (fd); return r; } static const sd_bus_vtable sysprofd_vtable[] = { SD_BUS_VTABLE_START (0), SD_BUS_METHOD ("PerfEventOpen", "a{sv}iit", "h", sysprofd_perf_event_open, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; int main (int argc, char *argv[]) { sd_bus_slot *slot = NULL; sd_bus *bus = NULL; int r; /* Connect to the system bus */ r = sd_bus_default_system (&bus); if (r < 0) { fprintf (stderr, "Failed to connect to system bus: %s\n", strerror (-r)); goto failure; } /* Install our object */ r = sd_bus_add_object_vtable (bus, &slot, "/org/gnome/Sysprof2", "org.gnome.Sysprof2", sysprofd_vtable, NULL); if (r < 0) { fprintf (stderr, "Failed to install object on bus: %s\n", strerror (-r)); goto failure; } /* Request our well-known name on the bus */ r = sd_bus_request_name (bus, "org.gnome.Sysprof2", 0); if (r < 0) { fprintf (stderr, "Failed to register name on the bus: %s\n", strerror (-r)); goto failure; } for (;;) { /* Process requests */ r = sd_bus_process (bus, NULL); if (r < 0) { fprintf (stderr, "Failed to process bus: %s\n", strerror (-r)); goto failure; } /* If we processed a request, continue processing */ if (r > 0) continue; /* Wait for the next request to process */ r = sd_bus_wait (bus, BUS_TIMEOUT_USEC); if (r < 0) { fprintf (stderr, "Failed to wait on bus: %s\n", strerror (-r)); goto failure; } /* * If we timed out, we can expire, we will be auto-started by * systemd or dbus on the next activation request. */ if (r == 0) break; } failure: sd_bus_slot_unref (slot); sd_bus_unref (bus); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }