diff options
author | Martin Pitt <martin.pitt@ubuntu.com> | 2015-07-29 15:16:01 +0200 |
---|---|---|
committer | Martin Pitt <martin.pitt@ubuntu.com> | 2015-07-29 15:16:01 +0200 |
commit | 7035cd9e226bce7909e1365039431a4d21e21045 (patch) | |
tree | 7402a205a32d8937bb50a8149b939e71028e7ff8 /src | |
parent | fb183854ab9b5774094a753ad3f46b653a9055da (diff) | |
download | systemd-7035cd9e226bce7909e1365039431a4d21e21045.tar.gz |
Imported Upstream version 223
Diffstat (limited to 'src')
208 files changed, 5202 insertions, 29427 deletions
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index c0863e4167..db1e7f3f37 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -88,6 +88,18 @@ struct boot_times { usec_t generators_finish_time; usec_t unitsload_start_time; usec_t unitsload_finish_time; + + /* + * If we're analyzing the user instance, all timestamps will be offset + * by its own start-up timestamp, which may be arbitrarily big. + * With "plot", this causes arbitrarily wide output SVG files which almost + * completely consist of empty space. Thus we cancel out this offset. + * + * This offset is subtracted from times above by acquire_boot_times(), + * but it still needs to be subtracted from unit-specific timestamps + * (so it is stored here for reference). + */ + usec_t reverse_offset; }; struct unit_times { @@ -188,95 +200,13 @@ static void free_unit_times(struct unit_times *t, unsigned n) { free(t); } -static int acquire_time_data(sd_bus *bus, struct unit_times **out) { - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - int r, c = 0; - struct unit_times *unit_times = NULL; - size_t size = 0; - UnitInfo u; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, &reply, - NULL); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - goto fail; - } +static void subtract_timestamp(usec_t *a, usec_t b) { + assert(a); - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) { - bus_log_parse_error(r); - goto fail; + if (*a > 0) { + assert(*a >= b); + *a -= b; } - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - struct unit_times *t; - - if (!GREEDY_REALLOC(unit_times, size, c+1)) { - r = log_oom(); - goto fail; - } - - t = unit_times+c; - t->name = NULL; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveExitTimestampMonotonic", - &t->activating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveEnterTimestampMonotonic", - &t->activated) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveExitTimestampMonotonic", - &t->deactivating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveEnterTimestampMonotonic", - &t->deactivated) < 0) { - r = -EIO; - goto fail; - } - - if (t->activated >= t->activating) - t->time = t->activated - t->activating; - else if (t->deactivated >= t->activating) - t->time = t->deactivated - t->activating; - else - t->time = 0; - - if (t->activating == 0) - continue; - - t->name = strdup(u.id); - if (t->name == NULL) { - r = log_oom(); - goto fail; - } - c++; - } - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - *out = unit_times; - return c; - -fail: - if (unit_times) - free_unit_times(unit_times, (unsigned) c); - return r; } static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { @@ -355,10 +285,30 @@ static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { return -EINPROGRESS; } - if (times.initrd_time) - times.kernel_done_time = times.initrd_time; - else - times.kernel_done_time = times.userspace_time; + if (arg_user) { + /* + * User-instance-specific timestamps processing + * (see comment to reverse_offset in struct boot_times). + */ + times.reverse_offset = times.userspace_time; + + times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; + subtract_timestamp(×.finish_time, times.reverse_offset); + + subtract_timestamp(×.security_start_time, times.reverse_offset); + subtract_timestamp(×.security_finish_time, times.reverse_offset); + + subtract_timestamp(×.generators_start_time, times.reverse_offset); + subtract_timestamp(×.generators_finish_time, times.reverse_offset); + + subtract_timestamp(×.unitsload_start_time, times.reverse_offset); + subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); + } else { + if (times.initrd_time) + times.kernel_done_time = times.initrd_time; + else + times.kernel_done_time = times.userspace_time; + } cached = true; @@ -378,6 +328,107 @@ static void free_host_info(struct host_info *hi) { free(hi); } +static int acquire_time_data(sd_bus *bus, struct unit_times **out) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + int r, c = 0; + struct boot_times *boot_times = NULL; + struct unit_times *unit_times = NULL; + size_t size = 0; + UnitInfo u; + + r = acquire_boot_times(bus, &boot_times); + if (r < 0) + goto fail; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, &reply, + NULL); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + goto fail; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + struct unit_times *t; + + if (!GREEDY_REALLOC(unit_times, size, c+1)) { + r = log_oom(); + goto fail; + } + + t = unit_times+c; + t->name = NULL; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveExitTimestampMonotonic", + &t->activating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveEnterTimestampMonotonic", + &t->activated) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveExitTimestampMonotonic", + &t->deactivating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveEnterTimestampMonotonic", + &t->deactivated) < 0) { + r = -EIO; + goto fail; + } + + subtract_timestamp(&t->activating, boot_times->reverse_offset); + subtract_timestamp(&t->activated, boot_times->reverse_offset); + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + + if (t->activated >= t->activating) + t->time = t->activated - t->activating; + else if (t->deactivated >= t->activating) + t->time = t->deactivated - t->activating; + else + t->time = 0; + + if (t->activating == 0) + continue; + + t->name = strdup(u.id); + if (t->name == NULL) { + r = log_oom(); + goto fail; + } + c++; + } + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + *out = unit_times; + return c; + +fail: + if (unit_times) + free_unit_times(unit_times, (unsigned) c); + return r; +} + static int acquire_host_info(sd_bus *bus, struct host_info **hi) { int r; struct host_info *host; @@ -450,10 +501,7 @@ static int pretty_boot_time(sd_bus *bus, char **_buf) { size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); - if (t->kernel_time > 0) - strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); - else - strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); + strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); ptr = strdup(buf); if (!ptr) diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index c79ad6520c..c8961de946 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -415,7 +415,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = write_string_file(saved, value); + r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE); if (r < 0) { log_error_errno(r, "Failed to write %s: %m", saved); return EXIT_FAILURE; diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c new file mode 100644 index 0000000000..bf9d8d4d7c --- /dev/null +++ b/src/basic/bitmap.c @@ -0,0 +1,198 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" + +#include "bitmap.h" + +struct Bitmap { + uint64_t *bitmaps; + size_t n_bitmaps; + size_t bitmaps_allocated; +}; + +/* Bitmaps are only meant to store relatively small numbers + * (corresponding to, say, an enum), so it is ok to limit + * the max entry. 64k should be plenty. */ +#define BITMAPS_MAX_ENTRY 0xffff + +/* This indicates that we reached the end of the bitmap */ +#define BITMAP_END ((unsigned) -1) + +#define BITMAP_NUM_TO_OFFSET(n) ((n) / (sizeof(uint64_t) * 8)) +#define BITMAP_NUM_TO_REM(n) ((n) % (sizeof(uint64_t) * 8)) +#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem)) + +Bitmap *bitmap_new(void) { + return new0(Bitmap, 1); +} + +void bitmap_free(Bitmap *b) { + if (!b) + return; + + free(b->bitmaps); + free(b); +} + +int bitmap_ensure_allocated(Bitmap **b) { + Bitmap *a; + + assert(b); + + if (*b) + return 0; + + a = bitmap_new(); + if (!a) + return -ENOMEM; + + *b = a; + + return 0; +} + +int bitmap_set(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + assert(b); + + /* we refuse to allocate huge bitmaps */ + if (n > BITMAPS_MAX_ENTRY) + return -ERANGE; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) { + if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1)) + return -ENOMEM; + + b->n_bitmaps = offset + 1; + } + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + b->bitmaps[offset] |= bitmask; + + return 0; +} + +void bitmap_unset(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + if (!b) + return; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) + return; + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + b->bitmaps[offset] &= ~bitmask; +} + +bool bitmap_isset(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + if (!b) + return false; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) + return false; + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + return !!(b->bitmaps[offset] & bitmask); +} + +bool bitmap_isclear(Bitmap *b) { + unsigned i; + + assert(b); + + for (i = 0; i < b->n_bitmaps; i++) + if (b->bitmaps[i] != 0) + return false; + + return true; +} + +void bitmap_clear(Bitmap *b) { + assert(b); + + b->n_bitmaps = 0; +} + +bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) { + uint64_t bitmask; + unsigned offset, rem; + + assert(i); + assert(n); + + if (!b || i->idx == BITMAP_END) + return false; + + offset = BITMAP_NUM_TO_OFFSET(i->idx); + rem = BITMAP_NUM_TO_REM(i->idx); + bitmask = UINT64_C(1) << rem; + + for (; offset < b->n_bitmaps; offset ++) { + if (b->bitmaps[offset]) { + for (; bitmask; bitmask <<= 1, rem ++) { + if (b->bitmaps[offset] & bitmask) { + *n = BITMAP_OFFSET_TO_NUM(offset, rem); + i->idx = *n + 1; + + return true; + } + } + } + + rem = 0; + bitmask = 1; + } + + i->idx = BITMAP_END; + + return false; +} + +bool bitmap_equal(Bitmap *a, Bitmap *b) { + + if (!a ^ !b) + return false; + + if (!a) + return true; + + if (a->n_bitmaps != b->n_bitmaps) + return false; + + return memcmp(a->bitmaps, b->bitmaps, sizeof(uint64_t) * a->n_bitmaps) == 0; +} diff --git a/src/basic/bitmap.h b/src/basic/bitmap.h new file mode 100644 index 0000000000..2874bc99f7 --- /dev/null +++ b/src/basic/bitmap.h @@ -0,0 +1,50 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" +#include "hashmap.h" + +typedef struct Bitmap Bitmap; + +Bitmap *bitmap_new(void); + +void bitmap_free(Bitmap *b); + +int bitmap_ensure_allocated(Bitmap **b); + +int bitmap_set(Bitmap *b, unsigned n); +void bitmap_unset(Bitmap *b, unsigned n); +bool bitmap_isset(Bitmap *b, unsigned n); +bool bitmap_isclear(Bitmap *b); +void bitmap_clear(Bitmap *b); + +bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n); + +bool bitmap_equal(Bitmap *a, Bitmap *b); + +#define BITMAP_FOREACH(n, b, i) \ + for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free); + +#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep) diff --git a/src/basic/capability.c b/src/basic/capability.c index 58f00e6dae..8dbe4da5bb 100644 --- a/src/basic/capability.c +++ b/src/basic/capability.c @@ -204,7 +204,7 @@ static int drop_from_file(const char *fn, uint64_t drop) { if (asprintf(&p, "%u %u", lo, hi) < 0) return -ENOMEM; - r = write_string_file(fn, p); + r = write_string_file(fn, p, WRITE_STRING_FILE_CREATE); free(p); return r; diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 439c5516dc..34a3060509 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -646,7 +646,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { snprintf(c, sizeof(c), PID_FMT"\n", pid); - return write_string_file_no_create(fs, c); + return write_string_file(fs, c, 0); } int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { @@ -820,7 +820,7 @@ int cg_install_release_agent(const char *controller, const char *agent) { sc = strstrip(contents); if (sc[0] == 0) { - r = write_string_file_no_create(fs, agent); + r = write_string_file(fs, agent, 0); if (r < 0) return r; } else if (!streq(sc, agent)) @@ -840,7 +840,7 @@ int cg_install_release_agent(const char *controller, const char *agent) { sc = strstrip(contents); if (streq(sc, "0")) { - r = write_string_file_no_create(fs, "1"); + r = write_string_file(fs, "1", 0); if (r < 0) return r; @@ -861,7 +861,7 @@ int cg_uninstall_release_agent(const char *controller) { if (r < 0) return r; - r = write_string_file_no_create(fs, "0"); + r = write_string_file(fs, "0", 0); if (r < 0) return r; @@ -872,7 +872,7 @@ int cg_uninstall_release_agent(const char *controller) { if (r < 0) return r; - r = write_string_file_no_create(fs, ""); + r = write_string_file(fs, "", 0); if (r < 0) return r; @@ -1708,7 +1708,7 @@ int cg_set_attribute(const char *controller, const char *path, const char *attri if (r < 0) return r; - return write_string_file_no_create(p, value); + return write_string_file(p, value, 0); } int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) { diff --git a/src/basic/copy.c b/src/basic/copy.c index 230e7e4d3f..e2d356d676 100644 --- a/src/basic/copy.c +++ b/src/basic/copy.c @@ -24,6 +24,7 @@ #include "util.h" #include "btrfs-util.h" +#include "strv.h" #include "copy.h" #define COPY_BUFFER_SIZE (16*1024) @@ -262,10 +263,13 @@ static int fd_copy_directory( (void) copy_xattr(dirfd(d), fdt); } - FOREACH_DIRENT(de, d, return -errno) { + FOREACH_DIRENT_ALL(de, d, return -errno) { struct stat buf; int q; + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { r = -errno; continue; diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c index 5ab36825c0..fcff753ada 100644 --- a/src/basic/exit-status.c +++ b/src/basic/exit-status.c @@ -151,6 +151,9 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { case EXIT_BUS_ENDPOINT: return "BUS_ENDPOINT"; + + case EXIT_SMACK_PROCESS_LABEL: + return "SMACK_PROCESS_LABEL"; } } diff --git a/src/basic/fileio-label.c b/src/basic/fileio-label.c index bec988ca78..f596f1d11f 100644 --- a/src/basic/fileio-label.c +++ b/src/basic/fileio-label.c @@ -31,7 +31,7 @@ int write_string_file_atomic_label(const char *fn, const char *line) { if (r < 0) return r; - r = write_string_file_atomic(fn, line); + r = write_string_file(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); mac_selinux_create_file_clear(); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 00fb6f8b5c..2216853777 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -27,14 +27,14 @@ #include "ctype.h" #include "fileio.h" -int write_string_stream(FILE *f, const char *line) { +int write_string_stream(FILE *f, const char *line, bool enforce_newline) { assert(f); assert(line); errno = 0; fputs(line, f); - if (!endswith(line, "\n")) + if (enforce_newline && !endswith(line, "\n")) fputc('\n', f); fflush(f); @@ -45,42 +45,7 @@ int write_string_stream(FILE *f, const char *line) { return 0; } -int write_string_file(const char *fn, const char *line) { - _cleanup_fclose_ FILE *f = NULL; - - assert(fn); - assert(line); - - f = fopen(fn, "we"); - if (!f) - return -errno; - - return write_string_stream(f, line); -} - -int write_string_file_no_create(const char *fn, const char *line) { - _cleanup_fclose_ FILE *f = NULL; - int fd; - - assert(fn); - assert(line); - - /* We manually build our own version of fopen(..., "we") that - * works without O_CREAT */ - fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - f = fdopen(fd, "we"); - if (!f) { - safe_close(fd); - return -errno; - } - - return write_string_stream(f, line); -} - -int write_string_file_atomic(const char *fn, const char *line) { +static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; int r; @@ -94,7 +59,7 @@ int write_string_file_atomic(const char *fn, const char *line) { fchmod_umask(fileno(f), 0644); - r = write_string_stream(f, line); + r = write_string_stream(f, line, enforce_newline); if (r >= 0) { if (rename(p, fn) < 0) r = -errno; @@ -106,6 +71,41 @@ int write_string_file_atomic(const char *fn, const char *line) { return r; } +int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { + _cleanup_fclose_ FILE *f = NULL; + + assert(fn); + assert(line); + + if (flags & WRITE_STRING_FILE_ATOMIC) { + assert(flags & WRITE_STRING_FILE_CREATE); + + return write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + } + + if (flags & WRITE_STRING_FILE_CREATE) { + f = fopen(fn, "we"); + if (!f) + return -errno; + } else { + int fd; + + /* We manually build our own version of fopen(..., "we") that + * works without O_CREAT */ + fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + f = fdopen(fd, "we"); + if (!f) { + safe_close(fd); + return -errno; + } + } + + return write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); +} + int read_one_line_file(const char *fn, char **line) { _cleanup_fclose_ FILE *f = NULL; char t[LINE_MAX], *c; @@ -786,7 +786,7 @@ int executable_is_script(const char *path, char **interpreter) { */ int get_status_field(const char *filename, const char *pattern, char **field) { _cleanup_free_ char *status = NULL; - char *t; + char *t, *f; size_t len; int r; @@ -820,9 +820,10 @@ int get_status_field(const char *filename, const char *pattern, char **field) { len = strcspn(t, WHITESPACE); - *field = strndup(t, len); - if (!*field) + f = strndup(t, len); + if (!f) return -ENOMEM; + *field = f; return 0; } diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 91d4a0d2d5..2e8148ff24 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -25,10 +25,14 @@ #include "macro.h" -int write_string_stream(FILE *f, const char *line); -int write_string_file(const char *fn, const char *line); -int write_string_file_no_create(const char *fn, const char *line); -int write_string_file_atomic(const char *fn, const char *line); +typedef enum { + WRITE_STRING_FILE_CREATE = 1, + WRITE_STRING_FILE_ATOMIC = 2, + WRITE_STRING_FILE_AVOID_NEWLINE = 4, +} WriteStringFileFlags; + +int write_string_stream(FILE *f, const char *line, bool enforce_newline); +int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags); int read_one_line_file(const char *fn, char **line); int read_full_file(const char *fn, char **contents, size_t *size); diff --git a/src/basic/macro.h b/src/basic/macro.h index 5fa17ed208..627d768b76 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -26,6 +26,7 @@ #include <sys/types.h> #include <sys/uio.h> #include <inttypes.h> +#include <stdbool.h> #define _printf_(a,b) __attribute__ ((format (printf, a, b))) #define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) @@ -406,12 +407,12 @@ do { \ #define IN_SET(x, y, ...) \ ({ \ - const typeof(y) _y = (y); \ - const typeof(_y) _x = (x); \ + static const typeof(y) _array[] = { (y), __VA_ARGS__ }; \ + const typeof(y) _x = (x); \ unsigned _i; \ bool _found = false; \ - for (_i = 0; _i < 1 + sizeof((const typeof(_x)[]) { __VA_ARGS__ })/sizeof(const typeof(_x)); _i++) \ - if (((const typeof(_x)[]) { _y, __VA_ARGS__ })[_i] == _x) { \ + for (_i = 0; _i < ELEMENTSOF(_array); _i++) \ + if (_array[_i] == _x) { \ _found = true; \ break; \ } \ @@ -461,6 +462,18 @@ do { \ #define GID_INVALID ((gid_t) -1) #define MODE_INVALID ((mode_t) -1) +static inline bool UID_IS_INVALID(uid_t uid) { + /* We consider both the old 16bit -1 user and the newer 32bit + * -1 user invalid, since they are or used to be incompatible + * with syscalls such as setresuid() or chown(). */ + + return uid == (uid_t) ((uint32_t) -1) || uid == (uid_t) ((uint16_t) -1); +} + +static inline bool GID_IS_INVALID(gid_t gid) { + return gid == (gid_t) ((uint32_t) -1) || gid == (gid_t) ((uint16_t) -1); +} + #define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ static inline void func##p(type *p) { \ if (*p) \ diff --git a/src/basic/missing.h b/src/basic/missing.h index be7f6186fc..ed6cd80c75 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -772,7 +772,7 @@ static inline int setns(int fd, int nstype) { #define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) #endif -#if !HAVE_DECL_IFLA_IPTUN_6RD_RELAY_PREFIXLEN +#if !HAVE_DECL_IFLA_IPTUN_ENCAP_DPORT #define IFLA_IPTUN_UNSPEC 0 #define IFLA_IPTUN_LINK 1 #define IFLA_IPTUN_LOCAL 2 @@ -788,11 +788,41 @@ static inline int setns(int fd, int nstype) { #define IFLA_IPTUN_6RD_RELAY_PREFIX 12 #define IFLA_IPTUN_6RD_PREFIXLEN 13 #define IFLA_IPTUN_6RD_RELAY_PREFIXLEN 14 -#define __IFLA_IPTUN_MAX 15 +#define IFLA_IPTUN_ENCAP_TYPE 15 +#define IFLA_IPTUN_ENCAP_FLAGS 16 +#define IFLA_IPTUN_ENCAP_SPORT 17 +#define IFLA_IPTUN_ENCAP_DPORT 18 + +#define __IFLA_IPTUN_MAX 19 #define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) #endif +#if !HAVE_DECL_IFLA_GRE_ENCAP_DPORT +#define IFLA_GRE_UNSPEC 0 +#define IFLA_GRE_LINK 1 +#define IFLA_GRE_IFLAGS 2 +#define IFLA_GRE_OFLAGS 3 +#define IFLA_GRE_IKEY 4 +#define IFLA_GRE_OKEY 5 +#define IFLA_GRE_LOCAL 6 +#define IFLA_GRE_REMOTE 7 +#define IFLA_GRE_TTL 8 +#define IFLA_GRE_TOS 9 +#define IFLA_GRE_PMTUDISC 10 +#define IFLA_GRE_ENCAP_LIMIT 11 +#define IFLA_GRE_FLOWINFO 12 +#define IFLA_GRE_FLAGS 13 +#define IFLA_GRE_ENCAP_TYPE 14 +#define IFLA_GRE_ENCAP_FLAGS 15 +#define IFLA_GRE_ENCAP_SPORT 16 +#define IFLA_GRE_ENCAP_DPORT 17 + +#define __IFLA_GRE_MAX 18 + +#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) +#endif + #if !HAVE_DECL_IFLA_BRIDGE_VLAN_INFO #define IFLA_BRIDGE_FLAGS 0 #define IFLA_BRIDGE_MODE 1 @@ -802,7 +832,7 @@ static inline int setns(int fd, int nstype) { #define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) #endif -#if !HAVE_DECL_IFLA_BRPORT_UNICAST_FLOOD +#if !HAVE_DECL_IFLA_BRPORT_LEARNING_SYNC #define IFLA_BRPORT_UNSPEC 0 #define IFLA_BRPORT_STATE 1 #define IFLA_BRPORT_PRIORITY 2 @@ -813,7 +843,9 @@ static inline int setns(int fd, int nstype) { #define IFLA_BRPORT_FAST_LEAVE 7 #define IFLA_BRPORT_LEARNING 8 #define IFLA_BRPORT_UNICAST_FLOOD 9 -#define __IFLA_BRPORT_MAX 10 +#define IFLA_BRPORT_PROXYARP 10 +#define IFLA_BRPORT_LEARNING_SYNC 11 +#define __IFLA_BRPORT_MAX 12 #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) #endif diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 8f49d65266..5cbfc145a4 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -656,9 +656,11 @@ int path_is_mount_point(const char *t, int flags) { canonical = canonicalize_file_name(t); if (!canonical) return -errno; + + t = canonical; } - r = path_get_parent(canonical ?: t, &parent); + r = path_get_parent(t, &parent); if (r < 0) return r; @@ -666,7 +668,7 @@ int path_is_mount_point(const char *t, int flags) { if (fd < 0) return -errno; - return fd_is_mount_point(fd, basename(canonical ?: t), flags); + return fd_is_mount_point(fd, basename(t), flags); } int path_is_read_only_fs(const char *path) { diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 2c05f2fee4..61f188467f 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -43,7 +43,10 @@ int get_process_state(pid_t pid) { assert(pid >= 0); p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; if (r < 0) return r; @@ -87,8 +90,11 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * p = procfs_file_alloca(pid, "cmdline"); f = fopen(p, "re"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -ESRCH; return -errno; + } if (max_length == 0) { size_t len = 0, allocated = 0; @@ -182,8 +188,11 @@ int is_kernel_thread(pid_t pid) { p = procfs_file_alloca(pid, "cmdline"); f = fopen(p, "re"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -ESRCH; return -errno; + } count = fread(&c, 1, 1, f); eof = feof(f); @@ -199,13 +208,18 @@ int is_kernel_thread(pid_t pid) { int get_process_capeff(pid_t pid, char **capeff) { const char *p; + int r; assert(capeff); assert(pid >= 0); p = procfs_file_alloca(pid, "status"); - return get_status_field(p, "\nCapEff:", capeff); + r = get_status_field(p, "\nCapEff:", capeff); + if (r == -ENOENT) + return -ESRCH; + + return r; } static int get_process_link_contents(const char *proc_file, char **name) { @@ -215,8 +229,10 @@ static int get_process_link_contents(const char *proc_file, char **name) { assert(name); r = readlink_malloc(proc_file, name); + if (r == -ENOENT) + return -ESRCH; if (r < 0) - return r == -ENOENT ? -ESRCH : r; + return r; return 0; } @@ -253,8 +269,11 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { p = procfs_file_alloca(pid, "status"); f = fopen(p, "re"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -ESRCH; return -errno; + } FOREACH_LINE(line, f, return -errno) { char *l; @@ -316,8 +335,11 @@ int get_process_environ(pid_t pid, char **env) { p = procfs_file_alloca(pid, "environ"); f = fopen(p, "re"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -ESRCH; return -errno; + } while ((c = fgetc(f)) != EOF) { if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) @@ -329,10 +351,13 @@ int get_process_environ(pid_t pid, char **env) { sz += cescape_char(c, outcome + sz); } - if (sz == 0) - return -ENOENT; + if (!outcome) { + outcome = strdup(""); + if (!outcome) + return -ENOMEM; + } else + outcome[sz] = '\0'; - outcome[sz] = '\0'; *env = outcome; outcome = NULL; @@ -355,6 +380,8 @@ int get_parent_of_pid(pid_t pid, pid_t *_ppid) { p = procfs_file_alloca(pid, "stat"); r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; if (r < 0) return r; @@ -475,8 +502,11 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { path = procfs_file_alloca(pid, "environ"); f = fopen(path, "re"); - if (!f) + if (!f) { + if (errno == ENOENT) + return -ESRCH; return -errno; + } l = strlen(field); r = 0; @@ -535,7 +565,7 @@ bool pid_is_alive(pid_t pid) { return false; r = get_process_state(pid); - if (r == -ENOENT || r == 'Z') + if (r == -ESRCH || r == 'Z') return false; return true; diff --git a/src/basic/smack-util.c b/src/basic/smack-util.c index 2e24b1ea99..047aa294f4 100644 --- a/src/basic/smack-util.c +++ b/src/basic/smack-util.c @@ -139,7 +139,7 @@ int mac_smack_apply_pid(pid_t pid, const char *label) { return 0; p = procfs_file_alloca(pid, "attr/current"); - r = write_string_file(p, label); + r = write_string_file(p, label, 0); if (r < 0) return r; #endif diff --git a/src/basic/util.c b/src/basic/util.c index aa912bde28..1c15fbc172 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -916,32 +916,573 @@ char *hexmem(const void *p, size_t l) { return r; } -void *unhexmem(const char *p, size_t l) { - uint8_t *r, *z; +int unhexmem(const char *p, size_t l, void **mem, size_t *len) { + _cleanup_free_ uint8_t *r = NULL; + uint8_t *z; const char *x; + assert(mem); + assert(len); assert(p); z = r = malloc((l + 1) / 2 + 1); if (!r) - return NULL; + return -ENOMEM; for (x = p; x < p + l; x += 2) { int a, b; a = unhexchar(x[0]); - if (x+1 < p + l) + if (a < 0) + return a; + else if (x+1 < p + l) { b = unhexchar(x[1]); - else + if (b < 0) + return b; + } else b = 0; *(z++) = (uint8_t) a << 4 | (uint8_t) b; } *z = 0; + + *mem = r; + r = NULL; + *len = (l + 1) / 2; + + return 0; +} + +/* https://tools.ietf.org/html/rfc4648#section-6 + * Notice that base32hex differs from base32 in the alphabet it uses. + * The distinction is that the base32hex representation preserves the + * order of the underlying data when compared as bytestrings, this is + * useful when representing NSEC3 hashes, as one can then verify the + * order of hashes directly from their representation. */ +char base32hexchar(int x) { + static const char table[32] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUV"; + + return table[x & 31]; +} + +int unbase32hexchar(char c) { + unsigned offset; + + if (c >= '0' && c <= '9') + return c - '0'; + + offset = '9' - '0' + 1; + + if (c >= 'A' && c <= 'V') + return c - 'A' + offset; + + return -EINVAL; +} + +char *base32hexmem(const void *p, size_t l, bool padding) { + char *r, *z; + const uint8_t *x; + size_t len; + + if (padding) + /* five input bytes makes eight output bytes, padding is added so we must round up */ + len = 8 * (l + 4) / 5; + else { + /* same, but round down as there is no padding */ + len = 8 * l / 5; + + switch (l % 5) { + case 4: + len += 7; + break; + case 3: + len += 5; + break; + case 2: + len += 4; + break; + case 1: + len += 2; + break; + } + } + + z = r = malloc(len + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ + x[3] == QQQQQQQQ; x[4] == WWWWWWWW */ + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ + *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ + *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5); /* 000QQWWW */ + *(z++) = base32hexchar((x[4] & 31)); /* 000WWWWW */ + } + + switch (l % 5) { + case 4: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ + *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ + *(z++) = base32hexchar((x[3] & 3) << 3); /* 000QQ000 */ + if (padding) + *(z++) = '='; + + break; + + case 3: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1); /* 000ZZZZ0 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + + case 2: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4); /* 000Y0000 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + + case 1: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + } + + *z = 0; + return r; +} + +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *r = NULL; + int a, b, c, d, e, f, g, h; + uint8_t *z; + const char *x; + size_t len; + unsigned pad = 0; + + assert(p); + + /* padding ensures any base32hex input has input divisible by 8 */ + if (padding && l % 8 != 0) + return -EINVAL; + + if (padding) { + /* strip the padding */ + while (l > 0 && p[l - 1] == '=' && pad < 7) { + pad ++; + l --; + } + } + + /* a group of eight input bytes needs five output bytes, in case of + padding we need to add some extra bytes */ + len = (l / 8) * 5; + + switch (l % 8) { + case 7: + len += 4; + break; + case 5: + len += 3; + break; + case 4: + len += 2; + break; + case 2: + len += 1; + break; + case 0: + break; + default: + return -EINVAL; + } + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 8) * 8; x += 8) { + /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW + e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */ + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + f = unbase32hexchar(x[5]); + if (f < 0) + return -EINVAL; + + g = unbase32hexchar(x[6]); + if (g < 0) + return -EINVAL; + + h = unbase32hexchar(x[7]); + if (h < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ + *(z++) = (uint8_t) g << 5 | (uint8_t) h; /* VVVRRRRR */ + } + + switch (l % 8) { + case 7: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + f = unbase32hexchar(x[5]); + if (f < 0) + return -EINVAL; + + g = unbase32hexchar(x[6]); + if (g < 0) + return -EINVAL; + + /* g == 000VV000 */ + if (g & 7) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ + + break; + case 5: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + /* e == 000SSSS0 */ + if (e & 1) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + + break; + case 4: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + /* d == 000W0000 */ + if (d & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + + break; + case 2: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 000YYY00 */ + if (b & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + + break; + case 0: + break; + default: + return -EINVAL; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + +/* https://tools.ietf.org/html/rfc4648#section-4 */ +char base64char(int x) { + static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return table[x & 63]; +} + +int unbase64char(char c) { + unsigned offset; + + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + offset = 'Z' - 'A' + 1; + + if (c >= 'a' && c <= 'z') + return c - 'a' + offset; + + offset += 'z' - 'a' + 1; + + if (c >= '0' && c <= '9') + return c - '0' + offset; + + offset += '9' - '0' + 1; + + if (c == '+') + return offset; + + offset ++; + + if (c == '/') + return offset; + + return -EINVAL; +} + +char *base64mem(const void *p, size_t l) { + char *r, *z; + const uint8_t *x; + + /* three input bytes makes four output bytes, padding is added so we must round up */ + z = r = malloc(4 * (l + 2) / 3 + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ + } + + switch (l % 3) { + case 2: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + *(z++) = '='; + + break; + case 1: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + *(z++) = '='; + *(z++) = '='; + + break; + } + + *z = 0; return r; } +int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *r = NULL; + int a, b, c, d; + uint8_t *z; + const char *x; + size_t len; + + assert(p); + + /* padding ensures any base63 input has input divisible by 4 */ + if (l % 4 != 0) + return -EINVAL; + + /* strip the padding */ + if (l > 0 && p[l - 1] == '=') + l --; + if (l > 0 && p[l - 1] == '=') + l --; + + /* a group of four input bytes needs three output bytes, in case of + padding we need to add two or three extra bytes */ + len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0); + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 4) * 4; x += 4) { + /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase64char(x[3]); + if (d < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + switch (l % 4) { + case 3: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + /* c == 00ZZZZ00 */ + if (c & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + + break; + case 2: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 00YY0000 */ + if (b & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + + break; + case 0: + + break; + default: + return -EINVAL; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + char octchar(int x) { return '0' + (x & 7); } @@ -2533,8 +3074,9 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { f = fdopen(fd, "we"); if (!f) { - unlink(t); + unlink_noerrno(t); free(t); + safe_close(fd); return -errno; } @@ -4716,7 +5258,7 @@ int update_reboot_param_file(const char *param) { if (param) { - r = write_string_file(REBOOT_PARAM_FILE, param); + r = write_string_file(REBOOT_PARAM_FILE, param, WRITE_STRING_FILE_CREATE); if (r < 0) log_error("Failed to write reboot param to " REBOOT_PARAM_FILE": %s", strerror(-r)); @@ -5192,13 +5734,19 @@ int unquote_first_word(const char **p, char **ret, UnquoteFlags flags) { case VALUE: if (c == 0) goto finish; - else if (c == '\'') + else if (c == '\'') { + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + state = SINGLE_QUOTE; - else if (c == '\\') + } else if (c == '\\') state = VALUE_ESCAPE; - else if (c == '\"') + else if (c == '\"') { + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + state = DOUBLE_QUOTE; - else if (strchr(WHITESPACE, c)) + } else if (strchr(WHITESPACE, c)) state = SPACE; else { if (!GREEDY_REALLOC(s, allocated, sz+2)) diff --git a/src/basic/util.h b/src/basic/util.h index a1d1dd15c3..c2e5cc610b 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -240,6 +240,10 @@ char octchar(int x) _const_; int unoctchar(char c) _const_; char decchar(int x) _const_; int undecchar(char c) _const_; +char base32hexchar(int x) _const_; +int unbase32hexchar(char c) _const_; +char base64char(int x) _const_; +int unbase64char(char c) _const_; char *cescape(const char *s); size_t cescape_char(char c, char *buf); @@ -614,7 +618,13 @@ static inline void *mempset(void *s, int c, size_t n) { } char *hexmem(const void *p, size_t l); -void *unhexmem(const char *p, size_t l); +int unhexmem(const char *p, size_t l, void **mem, size_t *len); + +char *base32hexmem(const void *p, size_t l, bool padding); +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); + +char *base64mem(const void *p, size_t l); +int unbase64mem(const char *p, size_t l, void **mem, size_t *len); char *strextend(char **x, ...) _sentinel_; char *strrep(const char *s, unsigned n); diff --git a/src/basic/virt.c b/src/basic/virt.c index 1299a75ed5..a8d26716a1 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -188,7 +188,7 @@ int detect_vm(const char **id) { _cleanup_free_ char *domcap = NULL, *cpuinfo_contents = NULL; static thread_local int cached_found = -1; static thread_local const char *cached_id = NULL; - const char *_id = NULL; + const char *_id = NULL, *_id_cpuid = NULL; int r; if (_likely_(cached_found >= 0)) { @@ -234,10 +234,26 @@ int detect_vm(const char **id) { /* this will set _id to "other" and return 0 for unknown hypervisors */ r = detect_vm_cpuid(&_id); - if (r != 0) + + /* finish when found a known hypervisor other than kvm */ + if (r < 0 || (r > 0 && !streq(_id, "kvm"))) goto finish; + _id_cpuid = _id; + r = detect_vm_dmi(&_id); + + /* kvm with and without Virtualbox */ + if (streq_ptr(_id_cpuid, "kvm")) { + if (r > 0 && streq(_id, "oracle")) + goto finish; + + _id = _id_cpuid; + r = 1; + goto finish; + } + + /* information from dmi */ if (r != 0) goto finish; diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c index 6028ed68c0..1e216f52bd 100644 --- a/src/binfmt/binfmt.c +++ b/src/binfmt/binfmt.c @@ -53,7 +53,7 @@ static int delete_rule(const char *rule) { if (!fn) return log_oom(); - return write_string_file(fn, "-1"); + return write_string_file(fn, "-1", 0); } static int apply_rule(const char *rule) { @@ -61,7 +61,7 @@ static int apply_rule(const char *rule) { delete_rule(rule); - r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule); + r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, 0); if (r < 0) return log_error_errno(r, "Failed to add binary format: %m"); @@ -191,7 +191,7 @@ int main(int argc, char *argv[]) { } /* Flush out all rules */ - write_string_file("/proc/sys/fs/binfmt_misc/status", "-1"); + write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0); STRV_FOREACH(f, files) { k = apply_file(*f, true); diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index ed69fb0cec..091ea375d3 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -294,6 +294,8 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { else if (r < 0) return r; + printf("\n"); + return 0; } @@ -888,7 +890,7 @@ static int install_loader_config(const char *esp_path) { f = fopen("/etc/machine-id", "re"); if (!f) - return -errno; + return errno == ENOENT ? 0 : -errno; if (fgets(line, sizeof(line), f) != NULL) { char *s; diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index eb1a4e3b66..e8cd8abd26 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1495,6 +1495,7 @@ static VOID config_entry_add_osx(Config *config) { static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_image, EFI_FILE *root_dir) { EFI_FILE_HANDLE linux_dir; EFI_STATUS err; + ConfigEntry *entry; err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &linux_dir, L"\\EFI\\Linux", EFI_FILE_MODE_READ, 0ULL); if (!EFI_ERROR(err)) { @@ -1504,6 +1505,7 @@ static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_ima EFI_FILE_INFO *f; CHAR8 *sections[] = { (UINT8 *)".osrel", + (UINT8 *)".cmdline", NULL }; UINTN offs[ELEMENTSOF(sections)-1] = {}; @@ -1517,6 +1519,7 @@ static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_ima CHAR16 *os_name = NULL; CHAR16 *os_id = NULL; CHAR16 *os_version = NULL; + CHAR16 *os_build = NULL; bufsize = sizeof(buf); err = uefi_call_wrapper(linux_dir->Read, 3, linux_dir, &bufsize, buf); @@ -1534,7 +1537,7 @@ static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_ima if (StriCmp(f->FileName + len - 4, L".efi") != 0) continue; - /* look for an .osrel section in the .efi binary */ + /* look for .osrel and .cmdline sections in the .efi binary */ err = pefile_locate_sections(linux_dir, f->FileName, sections, addrs, offs, szs); if (EFI_ERROR(err)) continue; @@ -1547,35 +1550,56 @@ static VOID config_entry_add_linux( Config *config, EFI_LOADED_IMAGE *loaded_ima line = content; while ((line = line_get_key_value(content, (CHAR8 *)"=", &pos, &key, &value))) { if (strcmpa((CHAR8 *)"PRETTY_NAME", key) == 0) { + FreePool(os_name); os_name = stra_to_str(value); continue; } if (strcmpa((CHAR8 *)"ID", key) == 0) { + FreePool(os_id); os_id = stra_to_str(value); continue; } if (strcmpa((CHAR8 *)"VERSION_ID", key) == 0) { + FreePool(os_version); os_version = stra_to_str(value); continue; } + + if (strcmpa((CHAR8 *)"BUILD_ID", key) == 0) { + FreePool(os_build); + os_build = stra_to_str(value); + continue; + } } - if (os_name && os_id && os_version) { + if (os_name && os_id && (os_version || os_build)) { CHAR16 *conf; CHAR16 *path; + CHAR16 *cmdline; - conf = PoolPrint(L"%s-%s", os_id, os_version); + conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build); path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); - config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); + entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); + + FreePool(content); + /* read the embedded cmdline file */ + len = file_read(linux_dir, f->FileName, offs[1], szs[1] - 1 , &content); + if (len > 0) { + cmdline = stra_to_str(content); + entry->options = cmdline; + cmdline = NULL; + } + FreePool(cmdline); FreePool(conf); FreePool(path); - FreePool(os_name); - FreePool(os_id); - FreePool(os_version); } + FreePool(os_name); + FreePool(os_id); + FreePool(os_version); + FreePool(os_build); FreePool(content); } uefi_call_wrapper(linux_dir->Close, 1, linux_dir); diff --git a/src/bootchart/bootchart.c b/src/bootchart/bootchart.c index 3360bc85be..1625d51fa8 100644 --- a/src/bootchart/bootchart.c +++ b/src/bootchart/bootchart.c @@ -387,9 +387,6 @@ int main(int argc, char *argv[]) { for (samples = 0; !exiting && samples < arg_samples_len; samples++) { int res; double sample_stop; - struct timespec req; - time_t newint_s; - long newint_ns; double elapsed; double timeleft; @@ -427,18 +424,17 @@ int main(int argc, char *argv[]) { elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0; timeleft = interval - elapsed; - newint_s = (time_t)(timeleft / 1000000000.0); - newint_ns = (long)(timeleft - (newint_s * 1000000000.0)); - /* * check if we have not consumed our entire timeslice. If we * do, don't sleep and take a new sample right away. * we'll lose all the missed samples and overrun our total * time */ - if (newint_ns > 0 || newint_s > 0) { - req.tv_sec = newint_s; - req.tv_nsec = newint_ns; + if (timeleft > 0) { + struct timespec req; + + req.tv_sec = (time_t)(timeleft / 1000000000.0); + req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0)); res = nanosleep(&req, NULL); if (res) { @@ -452,7 +448,7 @@ int main(int argc, char *argv[]) { } else { overrun++; /* calculate how many samples we lost and scrap them */ - arg_samples_len -= (int)(newint_ns / interval); + arg_samples_len -= (int)(-timeleft / interval); } LIST_PREPEND(link, head, sampledata); } diff --git a/src/bus-proxyd/driver.c b/src/bus-proxyd/driver.c index 4ac955da41..1cb5ea5008 100644 --- a/src/bus-proxyd/driver.c +++ b/src/bus-proxyd/driver.c @@ -33,6 +33,7 @@ #include "strv.h" #include "set.h" #include "driver.h" +#include "proxy.h" #include "synthesize.h" static int get_creds_by_name(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **_creds, sd_bus_error *error) { @@ -70,7 +71,7 @@ static int get_creds_by_message(sd_bus *bus, sd_bus_message *m, uint64_t mask, s return get_creds_by_name(bus, name, mask, _creds, error); } -int bus_proxy_process_driver(sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPolicy *sp, const struct ucred *ucred, Set *owned_names) { +int bus_proxy_process_driver(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPolicy *sp, const struct ucred *ucred, Set *owned_names) { int r; assert(a); @@ -189,7 +190,7 @@ int bus_proxy_process_driver(sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPoli if (r < 0) return synthetic_reply_method_errno(m, r, NULL); - r = sd_bus_add_match(a, NULL, match, NULL, NULL); + r = sd_bus_add_match(a, NULL, match, proxy_match, p); if (r < 0) return synthetic_reply_method_errno(m, r, NULL); diff --git a/src/bus-proxyd/driver.h b/src/bus-proxyd/driver.h index b8cedf5ce5..da3834f8b0 100644 --- a/src/bus-proxyd/driver.h +++ b/src/bus-proxyd/driver.h @@ -23,5 +23,6 @@ #include "sd-bus.h" #include "bus-xml-policy.h" +#include "proxy.h" -int bus_proxy_process_driver(sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPolicy *sp, const struct ucred *ucred, Set *owned_names); +int bus_proxy_process_driver(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPolicy *sp, const struct ucred *ucred, Set *owned_names); diff --git a/src/bus-proxyd/proxy.c b/src/bus-proxyd/proxy.c index 189ee969c7..c37b09b9c0 100644 --- a/src/bus-proxyd/proxy.c +++ b/src/bus-proxyd/proxy.c @@ -144,9 +144,17 @@ static int proxy_create_local(Proxy *p, int in_fd, int out_fd, bool negotiate_fd return 0; } +static int proxy_match_synthetic(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Proxy *p = userdata; + + p->synthetic_matched = true; + return 0; /* make sure to continue processing it in further handlers */ +} + /* - * dbus-1 clients receive NameOwnerChanged and directed signals without - * subscribing to them; install the matches to receive them on kdbus. + * We always need NameOwnerChanged so we can synthesize NameLost and + * NameAcquired. Furthermore, dbus-1 always passes unicast-signals through, so + * subscribe unconditionally. */ static int proxy_prepare_matches(Proxy *p) { _cleanup_free_ char *match = NULL; @@ -172,7 +180,7 @@ static int proxy_prepare_matches(Proxy *p) { if (!match) return log_oom(); - r = sd_bus_add_match(p->destination_bus, NULL, match, NULL, NULL); + r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); if (r < 0) return log_error_errno(r, "Failed to add match for NameLost: %m"); @@ -189,7 +197,7 @@ static int proxy_prepare_matches(Proxy *p) { if (!match) return log_oom(); - r = sd_bus_add_match(p->destination_bus, NULL, match, NULL, NULL); + r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); if (r < 0) return log_error_errno(r, "Failed to add match for NameAcquired: %m"); @@ -202,7 +210,7 @@ static int proxy_prepare_matches(Proxy *p) { if (!match) return log_oom(); - r = sd_bus_add_match(p->destination_bus, NULL, match, NULL, NULL); + r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); if (r < 0) log_error_errno(r, "Failed to add match for directed signals: %m"); /* FIXME: temporarily ignore error to support older kdbus versions */ @@ -679,11 +687,28 @@ static int patch_sender(sd_bus *a, sd_bus_message *m) { static int proxy_process_destination_to_local(Proxy *p) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + bool matched, matched_synthetic; int r; assert(p); + /* + * Usually, we would just take any message that the bus passes to us + * and forward it to the local connection. However, there are actually + * applications that fail if they receive broadcasts that they didn't + * subscribe to. Therefore, we actually emulate a real broadcast + * matching here, and discard any broadcasts that weren't matched. Our + * match-handlers remembers whether a message was matched by any rule, + * by marking it in @p->message_matched. + */ + r = sd_bus_process(p->destination_bus, &m); + + matched = p->message_matched; + matched_synthetic = p->synthetic_matched; + p->message_matched = false; + p->synthetic_matched = false; + if (r == -ECONNRESET || r == -ENOTCONN) /* Treat 'connection reset by peer' as clean exit condition */ return r; if (r < 0) { @@ -699,12 +724,21 @@ static int proxy_process_destination_to_local(Proxy *p) { if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) return -ECONNRESET; - r = synthesize_name_acquired(p->destination_bus, p->local_bus, m); + r = synthesize_name_acquired(p, p->destination_bus, p->local_bus, m); if (r == -ECONNRESET || r == -ENOTCONN) return r; if (r < 0) return log_error_errno(r, "Failed to synthesize message: %m"); + /* discard broadcasts that were not matched by any MATCH rule */ + if (!matched && !sd_bus_message_get_destination(m)) { + if (!matched_synthetic) + log_debug("Dropped unmatched broadcast: uid=" UID_FMT " gid=" GID_FMT " pid=" PID_FMT " message=%s path=%s interface=%s member=%s sender=%s destination=%s", + p->local_creds.uid, p->local_creds.gid, p->local_creds.pid, bus_message_type_to_string(m->header->type), + strna(m->path), strna(m->interface), strna(m->member), strna(m->sender), strna(m->destination)); + return 1; + } + patch_sender(p->destination_bus, m); if (p->policy) { @@ -788,7 +822,7 @@ static int proxy_process_local_to_destination(Proxy *p) { if (r > 0) return 1; - r = bus_proxy_process_driver(p->destination_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); + r = bus_proxy_process_driver(p, p->destination_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); if (r == -ECONNRESET || r == -ENOTCONN) return r; if (r < 0) @@ -834,6 +868,13 @@ static int proxy_process_local_to_destination(Proxy *p) { return 1; } +int proxy_match(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Proxy *p = userdata; + + p->message_matched = true; + return 0; /* make sure to continue processing it in further handlers */ +} + int proxy_run(Proxy *p) { int r; diff --git a/src/bus-proxyd/proxy.h b/src/bus-proxyd/proxy.h index ff278a2465..ccb951c109 100644 --- a/src/bus-proxyd/proxy.h +++ b/src/bus-proxyd/proxy.h @@ -39,6 +39,8 @@ struct Proxy { bool got_hello : 1; bool queue_overflow : 1; + bool message_matched : 1; + bool synthetic_matched : 1; }; int proxy_new(Proxy **out, int in_fd, int out_fd, const char *dest); @@ -46,6 +48,7 @@ Proxy *proxy_free(Proxy *p); int proxy_set_policy(Proxy *p, SharedPolicy *policy, char **configuration); int proxy_hello_policy(Proxy *p, uid_t original_uid); +int proxy_match(sd_bus_message *m, void *userdata, sd_bus_error *error); int proxy_run(Proxy *p); DEFINE_TRIVIAL_CLEANUP_FUNC(Proxy*, proxy_free); diff --git a/src/bus-proxyd/synthesize.c b/src/bus-proxyd/synthesize.c index 67bcc7a242..3ecedfd575 100644 --- a/src/bus-proxyd/synthesize.c +++ b/src/bus-proxyd/synthesize.c @@ -28,6 +28,7 @@ #include "bus-internal.h" #include "bus-message.h" #include "bus-util.h" +#include "bus-match.h" #include "synthesize.h" int synthetic_driver_send(sd_bus *b, sd_bus_message *m) { @@ -152,11 +153,12 @@ int synthetic_reply_method_return_strv(sd_bus_message *call, char **l) { return synthetic_driver_send(call->bus, m); } -int synthesize_name_acquired(sd_bus *a, sd_bus *b, sd_bus_message *m) { +int synthesize_name_acquired(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m) { _cleanup_bus_message_unref_ sd_bus_message *n = NULL; const char *name, *old_owner, *new_owner; int r; + assert(p); assert(a); assert(b); assert(m); @@ -216,5 +218,18 @@ int synthesize_name_acquired(sd_bus *a, sd_bus *b, sd_bus_message *m) { if (r < 0) return r; - return sd_bus_send(b, n, NULL); + /* + * Make sure to only forward NameLost/NameAcquired messages if they + * match an installed MATCH rule of the local client. We really must + * not send messages the client doesn't expect. + */ + + r = bus_match_run(b, &b->match_callbacks, n); + if (r >= 0 && p->message_matched) + r = sd_bus_send(b, n, NULL); + + p->message_matched = false; + p->synthetic_matched = false; + + return r; } diff --git a/src/bus-proxyd/synthesize.h b/src/bus-proxyd/synthesize.h index e850350bc5..b596daddf2 100644 --- a/src/bus-proxyd/synthesize.h +++ b/src/bus-proxyd/synthesize.h @@ -22,6 +22,7 @@ ***/ #include "sd-bus.h" +#include "proxy.h" int synthetic_driver_send(sd_bus *b, sd_bus_message *m); @@ -33,4 +34,4 @@ int synthetic_reply_method_errorf(sd_bus_message *call, const char *name, const int synthetic_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *p); int synthetic_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); -int synthesize_name_acquired(sd_bus *a, sd_bus *b, sd_bus_message *m); +int synthesize_name_acquired(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m); diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index 46a444340a..b8d1d2ccaf 100644 --- a/src/cgls/cgls.c +++ b/src/cgls/cgls.c @@ -197,19 +197,19 @@ int main(int argc, char *argv[]) { if (arg_machine) { char *m; const char *cgroup; - _cleanup_free_ char *scope = NULL; + _cleanup_free_ char *unit = NULL; _cleanup_free_ char *path = NULL; _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; m = strjoina("/run/systemd/machines/", arg_machine); - r = parse_env_file(m, NEWLINE, "SCOPE", &scope, NULL); + r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); if (r < 0) { log_error_errno(r, "Failed to get machine path: %m"); goto finish; } - path = unit_dbus_path_from_name(scope); + path = unit_dbus_path_from_name(unit); if (!path) { log_oom(); goto finish; @@ -219,7 +219,7 @@ int main(int argc, char *argv[]) { bus, "org.freedesktop.systemd1", path, - "org.freedesktop.systemd1.Scope", + endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service", "ControlGroup", &error, &reply, diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index d630e35882..f953c9e624 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -27,6 +27,7 @@ #include <unistd.h> #include <alloca.h> #include <getopt.h> +#include <signal.h> #include "path-util.h" #include "terminal-util.h" diff --git a/src/console/Makefile b/src/console/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/console/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile
\ No newline at end of file diff --git a/src/console/consoled-display.c b/src/console/consoled-display.c deleted file mode 100644 index 569c011dc0..0000000000 --- a/src/console/consoled-display.c +++ /dev/null @@ -1,81 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <errno.h> -#include <stdlib.h> -#include "consoled.h" -#include "grdev.h" -#include "list.h" -#include "macro.h" -#include "util.h" - -int display_new(Display **out, Session *s, grdev_display *display) { - _cleanup_(display_freep) Display *d = NULL; - - assert(out); - assert(s); - assert(display); - - d = new0(Display, 1); - if (!d) - return -ENOMEM; - - d->session = s; - d->grdev = display; - d->width = grdev_display_get_width(display); - d->height = grdev_display_get_height(display); - LIST_PREPEND(displays_by_session, d->session->display_list, d); - - grdev_display_enable(display); - - *out = d; - d = NULL; - return 0; -} - -Display *display_free(Display *d) { - if (!d) - return NULL; - - LIST_REMOVE(displays_by_session, d->session->display_list, d); - free(d); - - return NULL; -} - -void display_refresh(Display *d) { - assert(d); - - d->width = grdev_display_get_width(d->grdev); - d->height = grdev_display_get_height(d->grdev); -} - -void display_render(Display *d, Workspace *w) { - const grdev_display_target *target; - - assert(d); - assert(w); - - GRDEV_DISPLAY_FOREACH_TARGET(d->grdev, target) { - if (workspace_draw(w, target)) - grdev_display_flip_target(d->grdev, target); - } -} diff --git a/src/console/consoled-manager.c b/src/console/consoled-manager.c deleted file mode 100644 index 20424eb267..0000000000 --- a/src/console/consoled-manager.c +++ /dev/null @@ -1,284 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <errno.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "log.h" -#include "signal-util.h" -#include "util.h" -#include "consoled.h" -#include "idev.h" -#include "grdev.h" -#include "sysview.h" -#include "unifont.h" - -int manager_new(Manager **out) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(out); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - r = sd_event_set_watchdog(m->event, true); - if (r < 0) - return r; - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGQUIT, SIGINT, SIGWINCH, SIGCHLD, -1); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGQUIT, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sd_bus_open_system(&m->sysbus); - if (r < 0) - return r; - - r = sd_bus_attach_event(m->sysbus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return r; - - r = unifont_new(&m->uf); - if (r < 0) - return r; - - r = sysview_context_new(&m->sysview, - SYSVIEW_CONTEXT_SCAN_LOGIND | - SYSVIEW_CONTEXT_SCAN_EVDEV | - SYSVIEW_CONTEXT_SCAN_DRM, - m->event, - m->sysbus, - NULL); - if (r < 0) - return r; - - r = grdev_context_new(&m->grdev, m->event, m->sysbus); - if (r < 0) - return r; - - r = idev_context_new(&m->idev, m->event, m->sysbus); - if (r < 0) - return r; - - *out = m; - m = NULL; - return 0; -} - -Manager *manager_free(Manager *m) { - if (!m) - return NULL; - - assert(!m->workspace_list); - - m->idev = idev_context_unref(m->idev); - m->grdev = grdev_context_unref(m->grdev); - m->sysview = sysview_context_free(m->sysview); - m->uf = unifont_unref(m->uf); - m->sysbus = sd_bus_unref(m->sysbus); - m->event = sd_event_unref(m->event); - free(m); - - return NULL; -} - -static int manager_sysview_session_filter(Manager *m, sysview_event *event) { - const char *sid = event->session_filter.id; - _cleanup_free_ char *desktop = NULL; - int r; - - assert(sid); - - r = sd_session_get_desktop(sid, &desktop); - if (r < 0) - return 0; - - return streq(desktop, "systemd-console"); -} - -static int manager_sysview_session_add(Manager *m, sysview_event *event) { - sysview_session *session = event->session_add.session; - Session *s; - int r; - - r = sysview_session_take_control(session); - if (r < 0) - return log_error_errno(r, "Cannot request session control on '%s': %m", - sysview_session_get_name(session)); - - r = session_new(&s, m, session); - if (r < 0) { - log_error_errno(r, "Cannot create session on '%s': %m", - sysview_session_get_name(session)); - sysview_session_release_control(session); - return r; - } - - sysview_session_set_userdata(session, s); - - return 0; -} - -static int manager_sysview_session_remove(Manager *m, sysview_event *event) { - sysview_session *session = event->session_remove.session; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_free(s); - - return 0; -} - -static int manager_sysview_session_attach(Manager *m, sysview_event *event) { - sysview_session *session = event->session_attach.session; - sysview_device *device = event->session_attach.device; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_add_device(s, device); - - return 0; -} - -static int manager_sysview_session_detach(Manager *m, sysview_event *event) { - sysview_session *session = event->session_detach.session; - sysview_device *device = event->session_detach.device; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_remove_device(s, device); - - return 0; -} - -static int manager_sysview_session_refresh(Manager *m, sysview_event *event) { - sysview_session *session = event->session_refresh.session; - sysview_device *device = event->session_refresh.device; - struct udev_device *ud = event->session_refresh.ud; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_refresh_device(s, device, ud); - - return 0; -} - -static int manager_sysview_session_control(Manager *m, sysview_event *event) { - sysview_session *session = event->session_control.session; - int error = event->session_control.error; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - if (error < 0) { - log_error_errno(error, "Cannot take session control on '%s': %m", - sysview_session_get_name(session)); - session_free(s); - sysview_session_set_userdata(session, NULL); - return error; - } - - return 0; -} - -static int manager_sysview_fn(sysview_context *sysview, void *userdata, sysview_event *event) { - Manager *m = userdata; - int r; - - assert(m); - - switch (event->type) { - case SYSVIEW_EVENT_SESSION_FILTER: - r = manager_sysview_session_filter(m, event); - break; - case SYSVIEW_EVENT_SESSION_ADD: - r = manager_sysview_session_add(m, event); - break; - case SYSVIEW_EVENT_SESSION_REMOVE: - r = manager_sysview_session_remove(m, event); - break; - case SYSVIEW_EVENT_SESSION_ATTACH: - r = manager_sysview_session_attach(m, event); - break; - case SYSVIEW_EVENT_SESSION_DETACH: - r = manager_sysview_session_detach(m, event); - break; - case SYSVIEW_EVENT_SESSION_REFRESH: - r = manager_sysview_session_refresh(m, event); - break; - case SYSVIEW_EVENT_SESSION_CONTROL: - r = manager_sysview_session_control(m, event); - break; - default: - r = 0; - break; - } - - return r; -} - -int manager_run(Manager *m) { - int r; - - assert(m); - - r = sysview_context_start(m->sysview, manager_sysview_fn, m); - if (r < 0) - return r; - - r = sd_event_loop(m->event); - - sysview_context_stop(m->sysview); - return r; -} diff --git a/src/console/consoled-session.c b/src/console/consoled-session.c deleted file mode 100644 index 264a4d009a..0000000000 --- a/src/console/consoled-session.c +++ /dev/null @@ -1,279 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <errno.h> -#include <stdlib.h> -#include "consoled.h" -#include "grdev.h" -#include "idev.h" -#include "list.h" -#include "macro.h" -#include "sd-event.h" -#include "sysview.h" -#include "util.h" - -static bool session_feed_keyboard(Session *s, idev_data *data) { - idev_data_keyboard *kdata = &data->keyboard; - - if (!data->resync && kdata->value == 1 && kdata->n_syms == 1) { - uint32_t nr; - sysview_seat *seat; - - /* handle VT-switch requests */ - nr = 0; - - switch (kdata->keysyms[0]) { - case XKB_KEY_F1 ... XKB_KEY_F12: - if (IDEV_KBDMATCH(kdata, - IDEV_KBDMOD_CTRL | IDEV_KBDMOD_ALT, - kdata->keysyms[0])) - nr = kdata->keysyms[0] - XKB_KEY_F1 + 1; - break; - case XKB_KEY_XF86Switch_VT_1 ... XKB_KEY_XF86Switch_VT_12: - nr = kdata->keysyms[0] - XKB_KEY_XF86Switch_VT_1 + 1; - break; - } - - if (nr != 0) { - seat = sysview_session_get_seat(s->sysview); - sysview_seat_switch_to(seat, nr); - return true; - } - } - - return false; -} - -static bool session_feed(Session *s, idev_data *data) { - switch (data->type) { - case IDEV_DATA_KEYBOARD: - return session_feed_keyboard(s, data); - default: - return false; - } -} - -static int session_idev_fn(idev_session *idev, void *userdata, idev_event *event) { - Session *s = userdata; - - switch (event->type) { - case IDEV_EVENT_DEVICE_ADD: - idev_device_enable(event->device_add.device); - break; - case IDEV_EVENT_DEVICE_REMOVE: - idev_device_disable(event->device_remove.device); - break; - case IDEV_EVENT_DEVICE_DATA: - if (!session_feed(s, &event->device_data.data)) - workspace_feed(s->active_ws, &event->device_data.data); - break; - } - - return 0; -} - -static void session_grdev_fn(grdev_session *grdev, void *userdata, grdev_event *event) { - grdev_display *display; - Session *s = userdata; - Display *d; - int r; - - switch (event->type) { - case GRDEV_EVENT_DISPLAY_ADD: - display = event->display_add.display; - - r = display_new(&d, s, display); - if (r < 0) { - log_error_errno(r, "Cannot create display '%s' on '%s': %m", - grdev_display_get_name(display), sysview_session_get_name(s->sysview)); - break; - } - - grdev_display_set_userdata(display, d); - workspace_refresh(s->active_ws); - break; - case GRDEV_EVENT_DISPLAY_REMOVE: - display = event->display_remove.display; - d = grdev_display_get_userdata(display); - if (!d) - break; - - display_free(d); - workspace_refresh(s->active_ws); - break; - case GRDEV_EVENT_DISPLAY_CHANGE: - display = event->display_remove.display; - d = grdev_display_get_userdata(display); - if (!d) - break; - - display_refresh(d); - workspace_refresh(s->active_ws); - break; - case GRDEV_EVENT_DISPLAY_FRAME: - display = event->display_remove.display; - d = grdev_display_get_userdata(display); - if (!d) - break; - - session_dirty(s); - break; - } -} - -static int session_redraw_fn(sd_event_source *src, void *userdata) { - Session *s = userdata; - Display *d; - - LIST_FOREACH(displays_by_session, d, s->display_list) - display_render(d, s->active_ws); - - grdev_session_commit(s->grdev); - - return 0; -} - -int session_new(Session **out, Manager *m, sysview_session *session) { - _cleanup_(session_freep) Session *s = NULL; - int r; - - assert(out); - assert(m); - assert(session); - - s = new0(Session, 1); - if (!s) - return -ENOMEM; - - s->manager = m; - s->sysview = session; - - r = grdev_session_new(&s->grdev, - m->grdev, - GRDEV_SESSION_MANAGED, - sysview_session_get_name(session), - session_grdev_fn, - s); - if (r < 0) - return r; - - r = idev_session_new(&s->idev, - m->idev, - IDEV_SESSION_MANAGED, - sysview_session_get_name(session), - session_idev_fn, - s); - if (r < 0) - return r; - - r = workspace_new(&s->my_ws, m); - if (r < 0) - return r; - - s->active_ws = workspace_attach(s->my_ws, s); - - r = sd_event_add_defer(m->event, &s->redraw_src, session_redraw_fn, s); - if (r < 0) - return r; - - grdev_session_enable(s->grdev); - idev_session_enable(s->idev); - - *out = s; - s = NULL; - return 0; -} - -Session *session_free(Session *s) { - if (!s) - return NULL; - - assert(!s->display_list); - - sd_event_source_unref(s->redraw_src); - - workspace_detach(s->active_ws, s); - workspace_unref(s->my_ws); - - idev_session_free(s->idev); - grdev_session_free(s->grdev); - free(s); - - return NULL; -} - -void session_dirty(Session *s) { - int r; - - assert(s); - - r = sd_event_source_set_enabled(s->redraw_src, SD_EVENT_ONESHOT); - if (r < 0) - log_error_errno(r, "Cannot enable redraw-source: %m"); -} - -void session_add_device(Session *s, sysview_device *device) { - unsigned int type; - - assert(s); - assert(device); - - type = sysview_device_get_type(device); - switch (type) { - case SYSVIEW_DEVICE_DRM: - grdev_session_add_drm(s->grdev, sysview_device_get_ud(device)); - break; - case SYSVIEW_DEVICE_EVDEV: - idev_session_add_evdev(s->idev, sysview_device_get_ud(device)); - break; - } -} - -void session_remove_device(Session *s, sysview_device *device) { - unsigned int type; - - assert(s); - assert(device); - - type = sysview_device_get_type(device); - switch (type) { - case SYSVIEW_DEVICE_DRM: - grdev_session_remove_drm(s->grdev, sysview_device_get_ud(device)); - break; - case SYSVIEW_DEVICE_EVDEV: - idev_session_remove_evdev(s->idev, sysview_device_get_ud(device)); - break; - } -} - -void session_refresh_device(Session *s, sysview_device *device, struct udev_device *ud) { - unsigned int type; - - assert(s); - assert(device); - - type = sysview_device_get_type(device); - switch (type) { - case SYSVIEW_DEVICE_DRM: - grdev_session_hotplug_drm(s->grdev, sysview_device_get_ud(device)); - break; - } -} diff --git a/src/console/consoled-terminal.c b/src/console/consoled-terminal.c deleted file mode 100644 index 03447d1b92..0000000000 --- a/src/console/consoled-terminal.c +++ /dev/null @@ -1,358 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <errno.h> -#include <stdlib.h> -#include "consoled.h" -#include "list.h" -#include "macro.h" -#include "util.h" - -static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) { - Terminal *t = userdata; - int r; - - if (t->pty) { - r = pty_write(t->pty, buf, size); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) { - Terminal *t = userdata; - int r; - - switch (event) { - case PTY_CHILD: - log_debug("PTY child exited"); - t->pty = pty_unref(t->pty); - break; - case PTY_DATA: - r = term_screen_feed_text(t->screen, ptr, size); - if (r < 0) - log_error_errno(r, "Cannot update screen state: %m"); - - workspace_dirty(t->workspace); - break; - } - - return 0; -} - -int terminal_new(Terminal **out, Workspace *w) { - _cleanup_(terminal_freep) Terminal *t = NULL; - int r; - - assert(w); - - t = new0(Terminal, 1); - if (!t) - return -ENOMEM; - - t->workspace = w; - LIST_PREPEND(terminals_by_workspace, w->terminal_list, t); - - r = term_parser_new(&t->parser, true); - if (r < 0) - return r; - - r = term_screen_new(&t->screen, terminal_write_fn, t, NULL, NULL); - if (r < 0) - return r; - - r = term_screen_set_answerback(t->screen, "systemd-console"); - if (r < 0) - return r; - - if (out) - *out = t; - t = NULL; - return 0; -} - -Terminal *terminal_free(Terminal *t) { - if (!t) - return NULL; - - assert(t->workspace); - - if (t->pty) { - (void) pty_signal(t->pty, SIGHUP); - pty_close(t->pty); - pty_unref(t->pty); - } - term_screen_unref(t->screen); - term_parser_free(t->parser); - LIST_REMOVE(terminals_by_workspace, t->workspace->terminal_list, t); - free(t); - - return NULL; -} - -void terminal_resize(Terminal *t) { - uint32_t width, height, fw, fh; - int r; - - assert(t); - - width = t->workspace->width; - height = t->workspace->height; - fw = unifont_get_width(t->workspace->manager->uf); - fh = unifont_get_height(t->workspace->manager->uf); - - width = (fw > 0) ? width / fw : 0; - height = (fh > 0) ? height / fh : 0; - - if (t->pty) { - r = pty_resize(t->pty, width, height); - if (r < 0) - log_error_errno(r, "Cannot resize pty: %m"); - } - - r = term_screen_resize(t->screen, width, height); - if (r < 0) - log_error_errno(r, "Cannot resize screen: %m"); -} - -void terminal_run(Terminal *t) { - pid_t pid; - - assert(t); - - if (t->pty) - return; - - pid = pty_fork(&t->pty, - t->workspace->manager->event, - terminal_pty_fn, - t, - term_screen_get_width(t->screen), - term_screen_get_height(t->screen)); - if (pid < 0) { - log_error_errno(pid, "Cannot fork PTY: %m"); - return; - } else if (pid == 0) { - /* child */ - - char **argv = (char*[]){ - (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL, - NULL - }; - - setenv("TERM", "xterm-256color", 1); - setenv("COLORTERM", "systemd-console", 1); - - execve(argv[0], argv, environ); - log_error_errno(errno, "Cannot exec %s (%d): %m", argv[0], -errno); - _exit(1); - } -} - -static void terminal_feed_keyboard(Terminal *t, idev_data *data) { - idev_data_keyboard *kdata = &data->keyboard; - int r; - - if (!data->resync && (kdata->value == 1 || kdata->value == 2)) { - assert_cc(TERM_KBDMOD_CNT == (int)IDEV_KBDMOD_CNT); - assert_cc(TERM_KBDMOD_IDX_SHIFT == (int)IDEV_KBDMOD_IDX_SHIFT && - TERM_KBDMOD_IDX_CTRL == (int)IDEV_KBDMOD_IDX_CTRL && - TERM_KBDMOD_IDX_ALT == (int)IDEV_KBDMOD_IDX_ALT && - TERM_KBDMOD_IDX_LINUX == (int)IDEV_KBDMOD_IDX_LINUX && - TERM_KBDMOD_IDX_CAPS == (int)IDEV_KBDMOD_IDX_CAPS); - - r = term_screen_feed_keyboard(t->screen, - kdata->keysyms, - kdata->n_syms, - kdata->ascii, - kdata->codepoints, - kdata->mods); - if (r < 0) - log_error_errno(r, "Cannot feed keyboard data to screen: %m"); - } -} - -void terminal_feed(Terminal *t, idev_data *data) { - switch (data->type) { - case IDEV_DATA_KEYBOARD: - terminal_feed_keyboard(t, data); - break; - } -} - -static void terminal_fill(uint8_t *dst, - uint32_t width, - uint32_t height, - uint32_t stride, - uint32_t value) { - uint32_t i, j, *px; - - for (j = 0; j < height; ++j) { - px = (uint32_t*)dst; - - for (i = 0; i < width; ++i) - *px++ = value; - - dst += stride; - } -} - -static void terminal_blend(uint8_t *dst, - uint32_t width, - uint32_t height, - uint32_t dst_stride, - const uint8_t *src, - uint32_t src_stride, - uint32_t fg, - uint32_t bg) { - uint32_t i, j, *px; - - for (j = 0; j < height; ++j) { - px = (uint32_t*)dst; - - for (i = 0; i < width; ++i) { - if (!src || src[i / 8] & (1 << (7 - i % 8))) - *px = fg; - else - *px = bg; - - ++px; - } - - src += src_stride; - dst += dst_stride; - } -} - -typedef struct { - const grdev_display_target *target; - unifont *uf; - uint32_t cell_width; - uint32_t cell_height; - bool dirty; -} TerminalDrawContext; - -static int terminal_draw_cell(term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width) { - TerminalDrawContext *ctx = userdata; - const grdev_display_target *target = ctx->target; - grdev_fb *fb = target->back; - uint32_t xpos, ypos, width, height; - uint32_t fg, bg; - unifont_glyph g; - uint8_t *dst; - int r; - - if (n_ch > 0) { - r = unifont_lookup(ctx->uf, &g, *ch); - if (r < 0) - r = unifont_lookup(ctx->uf, &g, 0xfffd); - if (r < 0) - unifont_fallback(&g); - } - - xpos = x * ctx->cell_width; - ypos = y * ctx->cell_height; - - if (xpos >= fb->width || ypos >= fb->height) - return 0; - - width = MIN(fb->width - xpos, ctx->cell_width * ch_width); - height = MIN(fb->height - ypos, ctx->cell_height); - - term_attr_to_argb32(attr, &fg, &bg, NULL); - - ctx->dirty = true; - - dst = fb->maps[0]; - dst += fb->strides[0] * ypos + sizeof(uint32_t) * xpos; - - if (n_ch < 1) { - terminal_fill(dst, - width, - height, - fb->strides[0], - bg); - } else { - if (width > g.width) - terminal_fill(dst + sizeof(uint32_t) * g.width, - width - g.width, - height, - fb->strides[0], - bg); - if (height > g.height) - terminal_fill(dst + fb->strides[0] * g.height, - width, - height - g.height, - fb->strides[0], - bg); - - terminal_blend(dst, - width, - height, - fb->strides[0], - g.data, - g.stride, - fg, - bg); - } - - return 0; -} - -bool terminal_draw(Terminal *t, const grdev_display_target *target) { - TerminalDrawContext ctx = { }; - uint64_t age; - - assert(t); - assert(target); - - /* start up terminal on first frame */ - terminal_run(t); - - ctx.target = target; - ctx.uf = t->workspace->manager->uf; - ctx.cell_width = unifont_get_width(ctx.uf); - ctx.cell_height = unifont_get_height(ctx.uf); - ctx.dirty = false; - - if (target->front) { - /* if the frontbuffer is new enough, no reason to redraw */ - age = term_screen_get_age(t->screen); - if (age != 0 && age <= target->front->data.u64) - return false; - } else { - /* force flip if no frontbuffer is set, yet */ - ctx.dirty = true; - } - - term_screen_draw(t->screen, terminal_draw_cell, &ctx, &target->back->data.u64); - - return ctx.dirty; -} diff --git a/src/console/consoled-workspace.c b/src/console/consoled-workspace.c deleted file mode 100644 index 5e9e5c7c49..0000000000 --- a/src/console/consoled-workspace.c +++ /dev/null @@ -1,167 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <errno.h> -#include <stdlib.h> -#include "consoled.h" -#include "grdev.h" -#include "idev.h" -#include "list.h" -#include "macro.h" -#include "util.h" - -int workspace_new(Workspace **out, Manager *m) { - _cleanup_(workspace_unrefp) Workspace *w = NULL; - int r; - - assert(out); - - w = new0(Workspace, 1); - if (!w) - return -ENOMEM; - - w->ref = 1; - w->manager = m; - LIST_PREPEND(workspaces_by_manager, m->workspace_list, w); - - r = terminal_new(&w->current, w); - if (r < 0) - return r; - - *out = w; - w = NULL; - return 0; -} - -static void workspace_cleanup(Workspace *w) { - Terminal *t; - - assert(w); - assert(w->ref == 0); - assert(w->manager); - assert(!w->session_list); - - w->current = NULL; - while ((t = w->terminal_list)) - terminal_free(t); - - LIST_REMOVE(workspaces_by_manager, w->manager->workspace_list, w); - free(w); -} - -Workspace *workspace_ref(Workspace *w) { - assert(w); - - ++w->ref; - return w; -} - -Workspace *workspace_unref(Workspace *w) { - if (!w) - return NULL; - - assert(w->ref > 0); - - if (--w->ref == 0) - workspace_cleanup(w); - - return NULL; -} - -Workspace *workspace_attach(Workspace *w, Session *s) { - assert(w); - assert(s); - - LIST_PREPEND(sessions_by_workspace, w->session_list, s); - workspace_refresh(w); - return workspace_ref(w); -} - -Workspace *workspace_detach(Workspace *w, Session *s) { - assert(w); - assert(s); - assert(s->active_ws == w); - - LIST_REMOVE(sessions_by_workspace, w->session_list, s); - workspace_refresh(w); - return workspace_unref(w); -} - -void workspace_refresh(Workspace *w) { - uint32_t width, height; - Terminal *t; - Session *s; - Display *d; - - assert(w); - - width = 0; - height = 0; - - /* find out minimum dimension of all attached displays */ - LIST_FOREACH(sessions_by_workspace, s, w->session_list) { - LIST_FOREACH(displays_by_session, d, s->display_list) { - assert(d->width > 0 && d->height > 0); - - if (width == 0 || d->width < width) - width = d->width; - if (height == 0 || d->height < height) - height = d->height; - } - } - - /* either both are zero, or none is zero */ - assert(!(!width ^ !height)); - - /* update terminal-sizes if dimensions changed */ - if (w->width != width || w->height != height) { - w->width = width; - w->height = height; - - LIST_FOREACH(terminals_by_workspace, t, w->terminal_list) - terminal_resize(t); - - workspace_dirty(w); - } -} - -void workspace_dirty(Workspace *w) { - Session *s; - - assert(w); - - LIST_FOREACH(sessions_by_workspace, s, w->session_list) - session_dirty(s); -} - -void workspace_feed(Workspace *w, idev_data *data) { - assert(w); - assert(data); - - terminal_feed(w->current, data); -} - -bool workspace_draw(Workspace *w, const grdev_display_target *target) { - assert(w); - assert(target); - - return terminal_draw(w->current, target); -} diff --git a/src/console/consoled.c b/src/console/consoled.c deleted file mode 100644 index 9f69e8983f..0000000000 --- a/src/console/consoled.c +++ /dev/null @@ -1,66 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <errno.h> -#include <stdlib.h> -#include "sd-daemon.h" -#include "log.h" -#include "signal-util.h" -#include "consoled.h" - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto out; - } - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Could not create manager: %m"); - goto out; - } - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = manager_run(m); - if (r < 0) { - log_error_errno(r, "Cannot run manager: %m"); - goto out; - } - -out: - sd_notify(false, - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/console/consoled.h b/src/console/consoled.h deleted file mode 100644 index f85c1a0791..0000000000 --- a/src/console/consoled.h +++ /dev/null @@ -1,164 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include "grdev.h" -#include "idev.h" -#include "list.h" -#include "macro.h" -#include "pty.h" -#include "sd-bus.h" -#include "sd-event.h" -#include "sysview.h" -#include "term.h" -#include "unifont.h" - -typedef struct Manager Manager; -typedef struct Session Session; -typedef struct Display Display; -typedef struct Workspace Workspace; -typedef struct Terminal Terminal; - -/* - * Terminals - */ - -struct Terminal { - Workspace *workspace; - LIST_FIELDS(Terminal, terminals_by_workspace); - - term_utf8 utf8; - term_parser *parser; - term_screen *screen; - Pty *pty; -}; - -int terminal_new(Terminal **out, Workspace *w); -Terminal *terminal_free(Terminal *t); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Terminal*, terminal_free); - -void terminal_resize(Terminal *t); -void terminal_run(Terminal *t); -void terminal_feed(Terminal *t, idev_data *data); -bool terminal_draw(Terminal *t, const grdev_display_target *target); - -/* - * Workspaces - */ - -struct Workspace { - unsigned long ref; - Manager *manager; - LIST_FIELDS(Workspace, workspaces_by_manager); - - LIST_HEAD(Terminal, terminal_list); - Terminal *current; - - LIST_HEAD(Session, session_list); - uint32_t width; - uint32_t height; -}; - -int workspace_new(Workspace **out, Manager *m); -Workspace *workspace_ref(Workspace *w); -Workspace *workspace_unref(Workspace *w); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Workspace*, workspace_unref); - -Workspace *workspace_attach(Workspace *w, Session *s); -Workspace *workspace_detach(Workspace *w, Session *s); -void workspace_refresh(Workspace *w); - -void workspace_dirty(Workspace *w); -void workspace_feed(Workspace *w, idev_data *data); -bool workspace_draw(Workspace *w, const grdev_display_target *target); - -/* - * Displays - */ - -struct Display { - Session *session; - LIST_FIELDS(Display, displays_by_session); - grdev_display *grdev; - uint32_t width; - uint32_t height; -}; - -int display_new(Display **out, Session *s, grdev_display *grdev); -Display *display_free(Display *d); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Display*, display_free); - -void display_refresh(Display *d); -void display_render(Display *d, Workspace *w); - -/* - * Sessions - */ - -struct Session { - Manager *manager; - sysview_session *sysview; - grdev_session *grdev; - idev_session *idev; - - LIST_FIELDS(Session, sessions_by_workspace); - Workspace *my_ws; - Workspace *active_ws; - - LIST_HEAD(Display, display_list); - sd_event_source *redraw_src; -}; - -int session_new(Session **out, Manager *m, sysview_session *session); -Session *session_free(Session *s); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Session*, session_free); - -void session_dirty(Session *s); - -void session_add_device(Session *s, sysview_device *device); -void session_remove_device(Session *s, sysview_device *device); -void session_refresh_device(Session *s, sysview_device *device, struct udev_device *ud); - -/* - * Managers - */ - -struct Manager { - sd_event *event; - sd_bus *sysbus; - unifont *uf; - sysview_context *sysview; - grdev_context *grdev; - idev_context *idev; - LIST_HEAD(Workspace, workspace_list); -}; - -int manager_new(Manager **out); -Manager *manager_free(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -int manager_run(Manager *m); diff --git a/src/core/automount.c b/src/core/automount.c index d847dc1629..4af381b4b6 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -471,13 +471,20 @@ static int automount_send_ready(Automount *a, Set *tokens, int status) { return r; } +static int automount_start_expire(Automount *a); + int automount_update_mount(Automount *a, MountState old_state, MountState state) { + int r; + assert(a); switch (state) { case MOUNT_MOUNTED: case MOUNT_REMOUNTING: automount_send_ready(a, a->tokens, 0); + r = automount_start_expire(a); + if (r < 0) + log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); break; case MOUNT_DEAD: case MOUNT_UNMOUNTING: @@ -490,6 +497,7 @@ int automount_update_mount(Automount *a, MountState old_state, MountState state) case MOUNT_FAILED: if (old_state != state) automount_send_ready(a, a->tokens, -ENODEV); + (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); break; default: break; @@ -633,8 +641,6 @@ static void *expire_thread(void *p) { return NULL; } -static int automount_start_expire(Automount *a); - static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { Automount *a = AUTOMOUNT(userdata); _cleanup_(expire_data_freep) struct expire_data *data = NULL; @@ -672,7 +678,10 @@ static int automount_start_expire(Automount *a) { assert(a); - timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/10, USEC_PER_SEC); + if (a->timeout_idle_usec == 0) + return 0; + + timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/3, USEC_PER_SEC); if (a->expire_event_source) { r = sd_event_source_set_time(a->expire_event_source, timeout); @@ -730,10 +739,6 @@ static void automount_enter_runnning(Automount *a) { } } - r = automount_start_expire(a); - if (r < 0) - log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); - automount_set_state(a, AUTOMOUNT_RUNNING); return; @@ -904,6 +909,7 @@ static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, vo _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; union autofs_v5_packet_union packet; Automount *a = AUTOMOUNT(userdata); + struct stat st; int r; assert(a); @@ -963,6 +969,19 @@ static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, vo log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); goto fail; } + + /* Before we do anything, let's see if somebody is playing games with us? */ + if (lstat(a->where, &st) < 0) { + log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev == a->dev_id) { + log_unit_info(UNIT(a), "Automount point already unmounted?"); + automount_send_ready(a, a->expire_tokens, 0); + break; + } + r = manager_add_job(UNIT(a)->manager, JOB_STOP, UNIT_TRIGGER(UNIT(a)), JOB_REPLACE, true, &error, NULL); if (r < 0) { log_unit_warning(UNIT(a), "Failed to queue umount startup job: %s", bus_error_message(&error, r)); @@ -1066,7 +1085,6 @@ const UnitVTable automount_vtable = { .finished_start_job = { [JOB_DONE] = "Set up automount %s.", [JOB_FAILED] = "Failed to set up automount %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", }, .finished_stop_job = { [JOB_DONE] = "Unset automount %s.", diff --git a/src/core/busname.c b/src/core/busname.c index 2085721546..9530a87311 100644 --- a/src/core/busname.c +++ b/src/core/busname.c @@ -1065,13 +1065,10 @@ const UnitVTable busname_vtable = { .finished_start_job = { [JOB_DONE] = "Listening on %s.", [JOB_FAILED] = "Failed to listen on %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", - [JOB_TIMEOUT] = "Timed out starting %s.", }, .finished_stop_job = { [JOB_DONE] = "Closed %s.", [JOB_FAILED] = "Failed stopping %s.", - [JOB_TIMEOUT] = "Timed out stopping %s.", }, }, }; diff --git a/src/core/execute.c b/src/core/execute.c index c92db51330..21721dc240 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1446,7 +1446,7 @@ static int exec_child( * shouldn't trip up over that. */ sprintf(t, "%i", context->oom_score_adjust); - r = write_string_file("/proc/self/oom_score_adj", t); + r = write_string_file("/proc/self/oom_score_adj", t, 0); if (r == -EPERM || r == -EACCES) { log_open(); log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); diff --git a/src/core/job.c b/src/core/job.c index 1448e5b69a..15f5cc0cc9 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -495,10 +495,48 @@ static void job_change_type(Job *j, JobType newtype) { j->type = newtype; } +static int job_perform_on_unit(Job **j) { + /* While we execute this operation the job might go away (for + * example: because it finishes immediately or is replaced by a new, + * conflicting job.) To make sure we don't access a freed job later on + * we store the id here, so that we can verify the job is still + * valid. */ + Manager *m = (*j)->manager; + Unit *u = (*j)->unit; + JobType t = (*j)->type; + uint32_t id = (*j)->id; + int r; + + switch (t) { + case JOB_START: + r = unit_start(u); + break; + + case JOB_RESTART: + t = JOB_STOP; + case JOB_STOP: + r = unit_stop(u); + break; + + case JOB_RELOAD: + r = unit_reload(u); + break; + + default: + assert_not_reached("Invalid job type"); + } + + /* Log if the job still exists and the start/stop/reload function + * actually did something. */ + *j = manager_get_job(m, id); + if (*j && r > 0) + unit_status_emit_starting_stopping_reloading(u, t); + + return r; +} + int job_run_and_invalidate(Job *j) { int r; - uint32_t id; - Manager *m = j->manager; assert(j); assert(j->installed); @@ -517,23 +555,9 @@ int job_run_and_invalidate(Job *j) { job_set_state(j, JOB_RUNNING); job_add_to_dbus_queue(j); - /* While we execute this operation the job might go away (for - * example: because it is replaced by a new, conflicting - * job.) To make sure we don't access a freed job later on we - * store the id here, so that we can verify the job is still - * valid. */ - id = j->id; switch (j->type) { - case JOB_START: - r = unit_start(j->unit); - - /* If this unit cannot be started, then simply wait */ - if (r == -EBADR) - r = 0; - break; - case JOB_VERIFY_ACTIVE: { UnitActiveState t = unit_active_state(j->unit); if (UNIT_IS_ACTIVE_OR_RELOADING(t)) @@ -545,17 +569,19 @@ int job_run_and_invalidate(Job *j) { break; } + case JOB_START: case JOB_STOP: case JOB_RESTART: - r = unit_stop(j->unit); + r = job_perform_on_unit(&j); - /* If this unit cannot stopped, then simply wait. */ + /* If the unit type does not support starting/stopping, + * then simply wait. */ if (r == -EBADR) r = 0; break; case JOB_RELOAD: - r = unit_reload(j->unit); + r = job_perform_on_unit(&j); break; case JOB_NOP: @@ -566,7 +592,6 @@ int job_run_and_invalidate(Job *j) { assert_not_reached("Unknown job type"); } - j = manager_get_job(m, id); if (j) { if (r == -EALREADY) r = job_finish_and_invalidate(j, JOB_DONE, true); @@ -588,161 +613,110 @@ int job_run_and_invalidate(Job *j) { } _pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) { + const char *format; const UnitStatusMessageFormats *format_table; + static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Started %s.", + [JOB_TIMEOUT] = "Timed out starting %s.", + [JOB_FAILED] = "Failed to start %s.", + [JOB_DEPENDENCY] = "Dependency failed for %s.", + [JOB_ASSERT] = "Assertion failed for %s.", + [JOB_UNSUPPORTED] = "Starting of %s not supported.", + }; + static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Stopped %s.", + [JOB_FAILED] = "Stopped (with error) %s.", + [JOB_TIMEOUT] = "Timed out stoppping %s.", + }; + static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Reloaded %s.", + [JOB_FAILED] = "Reload failed for %s.", + [JOB_TIMEOUT] = "Timed out reloading %s.", + }; + /* When verify-active detects the unit is inactive, report it. + * Most likely a DEPEND warning from a requisiting unit will + * occur next and it's nice to see what was requisited. */ + static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = { + [JOB_SKIPPED] = "%s is not active.", + }; assert(u); assert(t >= 0); assert(t < _JOB_TYPE_MAX); - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (!format_table) - return NULL; + if (t == JOB_START || t == JOB_STOP || t == JOB_RESTART) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = t == JOB_START ? format_table->finished_start_job[result] : + format_table->finished_stop_job[result]; + if (format) + return format; + } + } + /* Return generic strings */ if (t == JOB_START) - return format_table->finished_start_job[result]; + return generic_finished_start_job[result]; else if (t == JOB_STOP || t == JOB_RESTART) - return format_table->finished_stop_job[result]; + return generic_finished_stop_job[result]; + else if (t == JOB_RELOAD) + return generic_finished_reload_job[result]; + else if (t == JOB_VERIFY_ACTIVE) + return generic_finished_verify_active_job[result]; return NULL; } -_pure_ static const char *job_get_status_message_format_try_harder(Unit *u, JobType t, JobResult result) { +static void job_print_status_message(Unit *u, JobType t, JobResult result) { const char *format; + static const char* const job_result_status_table[_JOB_RESULT_MAX] = { + [JOB_DONE] = ANSI_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, + [JOB_TIMEOUT] = ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, + [JOB_FAILED] = ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, + [JOB_DEPENDENCY] = ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, + [JOB_SKIPPED] = ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, + [JOB_ASSERT] = ANSI_HIGHLIGHT_YELLOW_ON "ASSERT" ANSI_HIGHLIGHT_OFF, + [JOB_UNSUPPORTED] = ANSI_HIGHLIGHT_YELLOW_ON "UNSUPP" ANSI_HIGHLIGHT_OFF, + }; assert(u); assert(t >= 0); assert(t < _JOB_TYPE_MAX); format = job_get_status_message_format(u, t, result); - if (format) - return format; - - /* Return generic strings */ - if (t == JOB_START) { - if (result == JOB_DONE) - return "Started %s."; - else if (result == JOB_TIMEOUT) - return "Timed out starting %s."; - else if (result == JOB_FAILED) - return "Failed to start %s."; - else if (result == JOB_DEPENDENCY) - return "Dependency failed for %s."; - else if (result == JOB_ASSERT) - return "Assertion failed for %s."; - else if (result == JOB_UNSUPPORTED) - return "Starting of %s not supported."; - } else if (t == JOB_STOP || t == JOB_RESTART) { - if (result == JOB_DONE) - return "Stopped %s."; - else if (result == JOB_FAILED) - return "Stopped (with error) %s."; - else if (result == JOB_TIMEOUT) - return "Timed out stoppping %s."; - } else if (t == JOB_RELOAD) { - if (result == JOB_DONE) - return "Reloaded %s."; - else if (result == JOB_FAILED) - return "Reload failed for %s."; - else if (result == JOB_TIMEOUT) - return "Timed out reloading %s."; - } - - return NULL; -} + if (!format) + return; -static void job_print_status_message(Unit *u, JobType t, JobResult result) { - const char *format; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); + if (result != JOB_DONE) + manager_flip_auto_status(u->manager, true); DISABLE_WARNING_FORMAT_NONLITERAL; + unit_status_printf(u, job_result_status_table[result], format); + REENABLE_WARNING; - if (t == JOB_START) { - format = job_get_status_message_format(u, t, result); - if (!format) - return; - - switch (result) { - - case JOB_DONE: - if (u->condition_result) - unit_status_printf(u, ANSI_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format); - break; - - case JOB_TIMEOUT: - manager_flip_auto_status(u->manager, true); - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format); - break; - - case JOB_FAILED: { - _cleanup_free_ char *quoted = NULL; - - quoted = shell_maybe_quote(u->id); - - manager_flip_auto_status(u->manager, true); - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON "FAILED" ANSI_HIGHLIGHT_OFF, format); - manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); - break; - } - - case JOB_DEPENDENCY: - manager_flip_auto_status(u->manager, true); - unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "DEPEND" ANSI_HIGHLIGHT_OFF, format); - break; - - case JOB_ASSERT: - manager_flip_auto_status(u->manager, true); - unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "ASSERT" ANSI_HIGHLIGHT_OFF, format); - break; - - case JOB_UNSUPPORTED: - manager_flip_auto_status(u->manager, true); - unit_status_printf(u, ANSI_HIGHLIGHT_YELLOW_ON "UNSUPP" ANSI_HIGHLIGHT_OFF, format); - break; - - default: - ; - } - - } else if (t == JOB_STOP || t == JOB_RESTART) { - - format = job_get_status_message_format(u, t, result); - if (!format) - return; - - switch (result) { - - case JOB_TIMEOUT: - manager_flip_auto_status(u->manager, true); - unit_status_printf(u, ANSI_HIGHLIGHT_RED_ON " TIME " ANSI_HIGHLIGHT_OFF, format); - break; - - case JOB_DONE: - case JOB_FAILED: - unit_status_printf(u, ANSI_GREEN_ON " OK " ANSI_HIGHLIGHT_OFF, format); - break; - - default: - ; - } - - } else if (t == JOB_VERIFY_ACTIVE) { + if (t == JOB_START && result == JOB_FAILED) { + _cleanup_free_ char *quoted = shell_maybe_quote(u->id); - /* When verify-active detects the unit is inactive, report it. - * Most likely a DEPEND warning from a requisiting unit will - * occur next and it's nice to see what was requisited. */ - if (result == JOB_SKIPPED) - unit_status_printf(u, ANSI_HIGHLIGHT_ON " INFO " ANSI_HIGHLIGHT_OFF, "%s is not active."); + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, + "See 'systemctl status %s' for details.", strna(quoted)); } - - REENABLE_WARNING; } static void job_log_status_message(Unit *u, JobType t, JobResult result) { const char *format; char buf[LINE_MAX]; + sd_id128_t mid; + static const int job_result_log_level[_JOB_RESULT_MAX] = { + [JOB_DONE] = LOG_INFO, + [JOB_CANCELED] = LOG_INFO, + [JOB_TIMEOUT] = LOG_ERR, + [JOB_FAILED] = LOG_ERR, + [JOB_DEPENDENCY] = LOG_WARNING, + [JOB_SKIPPED] = LOG_NOTICE, + [JOB_INVALID] = LOG_INFO, + [JOB_ASSERT] = LOG_WARNING, + [JOB_UNSUPPORTED] = LOG_WARNING, + }; assert(u); assert(t >= 0); @@ -754,7 +728,7 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { if (log_on_console()) return; - format = job_get_status_message_format_try_harder(u, t, result); + format = job_get_status_message_format(u, t, result); if (!format) return; @@ -762,32 +736,40 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { snprintf(buf, sizeof(buf), format, unit_description(u)); REENABLE_WARNING; - if (t == JOB_START) { - sd_id128_t mid; - + if (t == JOB_START) mid = result == JOB_DONE ? SD_MESSAGE_UNIT_STARTED : SD_MESSAGE_UNIT_FAILED; - log_struct(result == JOB_DONE ? LOG_INFO : LOG_ERR, - LOG_MESSAGE_ID(mid), + else if (t == JOB_STOP || t == JOB_RESTART) + mid = SD_MESSAGE_UNIT_STOPPED; + else if (t == JOB_RELOAD) + mid = SD_MESSAGE_UNIT_RELOADED; + else { + log_struct(job_result_log_level[result], LOG_UNIT_ID(u), LOG_MESSAGE("%s", buf), "RESULT=%s", job_result_to_string(result), NULL); + return; + } - } else if (t == JOB_STOP) - log_struct(result == JOB_DONE ? LOG_INFO : LOG_ERR, - LOG_MESSAGE_ID(SD_MESSAGE_UNIT_STOPPED), - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - "RESULT=%s", job_result_to_string(result), - NULL); + log_struct(job_result_log_level[result], + LOG_MESSAGE_ID(mid), + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + "RESULT=%s", job_result_to_string(result), + NULL); +} - else if (t == JOB_RELOAD) - log_struct(result == JOB_DONE ? LOG_INFO : LOG_ERR, - LOG_MESSAGE_ID(SD_MESSAGE_UNIT_RELOADED), - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - "RESULT=%s", job_result_to_string(result), - NULL); +static void job_emit_status_message(Unit *u, JobType t, JobResult result) { + + /* No message if the job did not actually do anything due to failed condition. */ + if (t == JOB_START && result == JOB_DONE && !u->condition_result) + return; + + job_log_status_message(u, t, result); + + /* Reload status messages have traditionally not been printed to console. */ + if (t != JOB_RELOAD) + job_print_status_message(u, t, result); } static void job_fail_dependencies(Unit *u, UnitDependency d) { @@ -825,8 +807,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) { log_unit_debug(u, "Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result)); - job_print_status_message(u, t, result); - job_log_status_message(u, t, result); + job_emit_status_message(u, t, result); job_add_to_dbus_queue(j); diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c index b3d22840cf..8e26362546 100644 --- a/src/core/machine-id-setup.c +++ b/src/core/machine-id-setup.c @@ -260,7 +260,7 @@ int machine_id_setup(const char *root) { * /run/machine-id as a replacement */ RUN_WITH_UMASK(0022) { - r = write_string_file(run_machine_id, id); + r = write_string_file(run_machine_id, id, WRITE_STRING_FILE_CREATE); } if (r < 0) { (void) unlink(run_machine_id); diff --git a/src/core/main.c b/src/core/main.c index 523f0ce020..6ae8b51544 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -685,6 +685,26 @@ static int parse_config_file(void) { return 0; } +static void manager_set_defaults(Manager *m) { + + assert(m); + + m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; + m->default_std_output = arg_default_std_output; + m->default_std_error = arg_default_std_error; + m->default_timeout_start_usec = arg_default_timeout_start_usec; + m->default_timeout_stop_usec = arg_default_timeout_stop_usec; + m->default_restart_usec = arg_default_restart_usec; + m->default_start_limit_interval = arg_default_start_limit_interval; + m->default_start_limit_burst = arg_default_start_limit_burst; + m->default_cpu_accounting = arg_default_cpu_accounting; + m->default_blockio_accounting = arg_default_blockio_accounting; + m->default_memory_accounting = arg_default_memory_accounting; + + manager_set_default_rlimits(m, arg_default_rlimit); + manager_environment_add(m, NULL, arg_default_environment); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -1203,7 +1223,7 @@ static int write_container_id(void) { if (isempty(c)) return 0; - return write_string_file("/run/systemd/container", c); + return write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); } int main(int argc, char *argv[]) { @@ -1630,28 +1650,15 @@ int main(int argc, char *argv[]) { } m->confirm_spawn = arg_confirm_spawn; - m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; - m->default_std_output = arg_default_std_output; - m->default_std_error = arg_default_std_error; - m->default_restart_usec = arg_default_restart_usec; - m->default_timeout_start_usec = arg_default_timeout_start_usec; - m->default_timeout_stop_usec = arg_default_timeout_stop_usec; - m->default_start_limit_interval = arg_default_start_limit_interval; - m->default_start_limit_burst = arg_default_start_limit_burst; - m->default_cpu_accounting = arg_default_cpu_accounting; - m->default_blockio_accounting = arg_default_blockio_accounting; - m->default_memory_accounting = arg_default_memory_accounting; m->runtime_watchdog = arg_runtime_watchdog; m->shutdown_watchdog = arg_shutdown_watchdog; - m->userspace_timestamp = userspace_timestamp; m->kernel_timestamp = kernel_timestamp; m->initrd_timestamp = initrd_timestamp; m->security_start_timestamp = security_start_timestamp; m->security_finish_timestamp = security_finish_timestamp; - manager_set_default_rlimits(m, arg_default_rlimit); - manager_environment_add(m, NULL, arg_default_environment); + manager_set_defaults(m); manager_set_show_status(m, arg_show_status); manager_set_first_boot(m, empty_etc); @@ -1763,6 +1770,13 @@ int main(int argc, char *argv[]) { case MANAGER_RELOAD: log_info("Reloading."); + + r = parse_config_file(); + if (r < 0) + log_error("Failed to parse config file."); + + manager_set_defaults(m); + r = manager_reload(m); if (r < 0) log_error_errno(r, "Failed to reload: %m"); diff --git a/src/core/mount.c b/src/core/mount.c index 851b41351e..c0d1cdfbd4 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -834,8 +834,6 @@ static void mount_enter_unmounting(Mount *m) { m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; r = exec_command_set(m->control_command, UMOUNT_PATH, m->where, NULL); - if (r >= 0 && UNIT(m)->manager->running_as == MANAGER_SYSTEM) - r = exec_command_append(m->control_command, "-n", NULL); if (r < 0) goto fail; @@ -886,8 +884,6 @@ static void mount_enter_mounting(Mount *m) { r = exec_command_set(m->control_command, MOUNT_PATH, m->parameters_fragment.what, m->where, NULL); - if (r >= 0 && UNIT(m)->manager->running_as == MANAGER_SYSTEM) - r = exec_command_append(m->control_command, "-n", NULL); if (r >= 0 && m->sloppy_options) r = exec_command_append(m->control_command, "-s", NULL); if (r >= 0 && m->parameters_fragment.fstype) @@ -934,8 +930,6 @@ static void mount_enter_remounting(Mount *m) { r = exec_command_set(m->control_command, MOUNT_PATH, m->parameters_fragment.what, m->where, "-o", o, NULL); - if (r >= 0 && UNIT(m)->manager->running_as == MANAGER_SYSTEM) - r = exec_command_append(m->control_command, "-n", NULL); if (r >= 0 && m->sloppy_options) r = exec_command_append(m->control_command, "-s", NULL); if (r >= 0 && m->parameters_fragment.fstype) @@ -1025,7 +1019,7 @@ static int mount_reload(Unit *u) { assert(m->state == MOUNT_MOUNTED); mount_enter_remounting(m); - return 0; + return 1; } static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { @@ -1897,7 +1891,6 @@ const UnitVTable mount_vtable = { .finished_start_job = { [JOB_DONE] = "Mounted %s.", [JOB_FAILED] = "Failed to mount %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", [JOB_TIMEOUT] = "Timed out mounting %s.", }, .finished_stop_job = { diff --git a/src/core/path.c b/src/core/path.c index 6d26d89e82..20995d920c 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -426,7 +426,7 @@ static void path_set_state(Path *p, PathState state) { path_unwatch(p); if (state != old_state) - log_debug("Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state)); + log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state)); unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true); } diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c index e9a9a020de..50a90b0bac 100644 --- a/src/core/selinux-access.c +++ b/src/core/selinux-access.c @@ -302,12 +302,12 @@ int mac_selinux_unit_access_check_strv( int r; STRV_FOREACH(i, units) { - u = manager_get_unit(m, *i); - if (u) { - r = mac_selinux_unit_access_check(u, message, permission, error); - if (r < 0) - return r; - } + r = manager_load_unit(m, *i, NULL, error, &u); + if (r < 0) + return r; + r = mac_selinux_unit_access_check(u, message, permission, error); + if (r < 0) + return r; } #endif return 0; diff --git a/src/core/service.c b/src/core/service.c index d72ff54daa..b790ec98be 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -401,7 +401,6 @@ static int service_add_fd_store_set(Service *s, FDSet *fds) { r = service_add_fd_store(s, fd); if (r < 0) return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); - if (r > 0) { log_unit_debug(UNIT(s), "Added fd to fd store."); fd = -1; @@ -576,8 +575,10 @@ static int service_add_extras(Service *s) { return r; r = unit_watch_bus_name(UNIT(s), s->bus_name); + if (r == -EEXIST) + return log_unit_error_errno(UNIT(s), r, "Two services allocated for the same bus name %s, refusing operation.", s->bus_name); if (r < 0) - return r; + return log_unit_error_errno(UNIT(s), r, "Cannot watch bus name %s: %m", s->bus_name); } if (UNIT(s)->default_dependencies) { @@ -1974,7 +1975,7 @@ static int service_reload(Unit *u) { assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED); service_enter_reload(s); - return 0; + return 1; } _pure_ static bool service_can_reload(Unit *u) { @@ -3229,13 +3230,10 @@ const UnitVTable service_vtable = { .finished_start_job = { [JOB_DONE] = "Started %s.", [JOB_FAILED] = "Failed to start %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", - [JOB_TIMEOUT] = "Timed out starting %s.", }, .finished_stop_job = { [JOB_DONE] = "Stopped %s.", [JOB_FAILED] = "Stopped (with error) %s.", - [JOB_TIMEOUT] = "Timed out stopping %s.", }, }, }; diff --git a/src/core/slice.c b/src/core/slice.c index e52bf71515..064eb5d933 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -297,7 +297,6 @@ const UnitVTable slice_vtable = { .status_message_formats = { .finished_start_job = { [JOB_DONE] = "Created slice %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", }, .finished_stop_job = { [JOB_DONE] = "Removed slice %s.", diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c index ddb02a1580..cbe7d0b4a9 100644 --- a/src/core/smack-setup.c +++ b/src/core/smack-setup.c @@ -221,7 +221,7 @@ int mac_smack_setup(bool *loaded_policy) { } #ifdef SMACK_RUN_LABEL - r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL); + r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); if (r) log_warning("Failed to set SMACK label \"%s\" on self: %s", SMACK_RUN_LABEL, strerror(-r)); diff --git a/src/core/socket.c b/src/core/socket.c index 693cbc6080..87631f8753 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -2722,7 +2722,6 @@ const UnitVTable socket_vtable = { .finished_start_job = { [JOB_DONE] = "Listening on %s.", [JOB_FAILED] = "Failed to listen on %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", [JOB_TIMEOUT] = "Timed out starting %s.", }, .finished_stop_job = { diff --git a/src/core/swap.c b/src/core/swap.c index 193c8c3767..0bc3827ff0 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -1505,7 +1505,6 @@ const UnitVTable swap_vtable = { .finished_start_job = { [JOB_DONE] = "Activated swap %s.", [JOB_FAILED] = "Failed to activate swap %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", [JOB_TIMEOUT] = "Timed out activating swap %s.", }, .finished_stop_job = { diff --git a/src/core/target.c b/src/core/target.c index 8817ef21c4..b492a7c4c7 100644 --- a/src/core/target.c +++ b/src/core/target.c @@ -227,7 +227,6 @@ const UnitVTable target_vtable = { .status_message_formats = { .finished_start_job = { [JOB_DONE] = "Reached target %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", }, .finished_stop_job = { [JOB_DONE] = "Stopped target %s.", diff --git a/src/core/unit.c b/src/core/unit.c index fac017c57d..dd5e801285 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1318,42 +1318,28 @@ static bool unit_assert_test(Unit *u) { } _pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { - const UnitStatusMessageFormats *format_table; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - if (t != JOB_START && t != JOB_STOP) - return NULL; - - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (!format_table) - return NULL; - - return format_table->starting_stopping[t == JOB_STOP]; -} - -_pure_ static const char *unit_get_status_message_format_try_harder(Unit *u, JobType t) { const char *format; + const UnitStatusMessageFormats *format_table; assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); + assert(t == JOB_START || t == JOB_STOP || t == JOB_RELOAD); - format = unit_get_status_message_format(u, t); - if (format) - return format; + if (t != JOB_RELOAD) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = format_table->starting_stopping[t == JOB_STOP]; + if (format) + return format; + } + } /* Return generic strings */ if (t == JOB_START) return "Starting %s."; else if (t == JOB_STOP) return "Stopping %s."; - else if (t == JOB_RELOAD) + else return "Reloading %s."; - - return NULL; } static void unit_status_print_starting_stopping(Unit *u, JobType t) { @@ -1361,12 +1347,7 @@ static void unit_status_print_starting_stopping(Unit *u, JobType t) { assert(u); - /* We only print status messages for selected units on - * selected operations. */ - format = unit_get_status_message_format(u, t); - if (!format) - return; DISABLE_WARNING_FORMAT_NONLITERAL; unit_status_printf(u, "", format); @@ -1388,9 +1369,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { /* We log status messages for all units and all operations. */ - format = unit_get_status_message_format_try_harder(u, t); - if (!format) - return; + format = unit_get_status_message_format(u, t); DISABLE_WARNING_FORMAT_NONLITERAL; snprintf(buf, sizeof(buf), format, unit_description(u)); @@ -1413,6 +1392,15 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { NULL); } +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { + + unit_status_log_starting_stopping_reloading(u, t); + + /* Reload status messages have traditionally not been printed to console. */ + if (t != JOB_RELOAD) + unit_status_print_starting_stopping(u, t); +} + /* Errors: * -EBADR: This unit type does not support starting. * -EALREADY: Unit is already started. @@ -1423,7 +1411,6 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { int unit_start(Unit *u) { UnitActiveState state; Unit *following; - int r; assert(u); @@ -1477,14 +1464,7 @@ int unit_start(Unit *u) { unit_add_to_dbus_queue(u); - r = UNIT_VTABLE(u)->start(u); - if (r <= 0) - return r; - - /* Log if the start function actually did something */ - unit_status_log_starting_stopping_reloading(u, JOB_START); - unit_status_print_starting_stopping(u, JOB_START); - return r; + return UNIT_VTABLE(u)->start(u); } bool unit_can_start(Unit *u) { @@ -1508,7 +1488,6 @@ bool unit_can_isolate(Unit *u) { int unit_stop(Unit *u) { UnitActiveState state; Unit *following; - int r; assert(u); @@ -1527,13 +1506,7 @@ int unit_stop(Unit *u) { unit_add_to_dbus_queue(u); - r = UNIT_VTABLE(u)->stop(u); - if (r <= 0) - return r; - - unit_status_log_starting_stopping_reloading(u, JOB_STOP); - unit_status_print_starting_stopping(u, JOB_STOP); - return r; + return UNIT_VTABLE(u)->stop(u); } /* Errors: @@ -1544,7 +1517,6 @@ int unit_stop(Unit *u) { int unit_reload(Unit *u) { UnitActiveState state; Unit *following; - int r; assert(u); @@ -1571,12 +1543,7 @@ int unit_reload(Unit *u) { unit_add_to_dbus_queue(u); - r = UNIT_VTABLE(u)->reload(u); - if (r <= 0) - return r; - - unit_status_log_starting_stopping_reloading(u, JOB_RELOAD); - return r; + return UNIT_VTABLE(u)->reload(u); } bool unit_can_reload(Unit *u) { diff --git a/src/core/unit.h b/src/core/unit.h index 9491ef64f9..e60168267f 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -544,6 +544,7 @@ int unit_add_node_link(Unit *u, const char *what, bool wants); int unit_coldplug(Unit *u); void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) _printf_(3, 0); +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t); bool unit_need_daemon_reload(Unit *u); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index cda96d484a..3805b29437 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -415,7 +415,7 @@ static int process_hostname(void) { return 0; mkdir_parents(etc_hostname, 0755); - r = write_string_file(etc_hostname, arg_hostname); + r = write_string_file(etc_hostname, arg_hostname, WRITE_STRING_FILE_CREATE); if (r < 0) return log_error_errno(r, "Failed to write %s: %m", etc_hostname); @@ -436,7 +436,7 @@ static int process_machine_id(void) { return 0; mkdir_parents(etc_machine_id, 0755); - r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id)); + r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), WRITE_STRING_FILE_CREATE); if (r < 0) return log_error_errno(r, "Failed to write machine id: %m"); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index b46e160888..da5f3b647a 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -183,7 +183,8 @@ static int add_cryptsetup(const char *id, const char *what, bool rw, char **devi r = write_string_file(p, "# Automatically generated by systemd-gpt-auto-generator\n\n" "[Unit]\n" - "JobTimeoutSec=0\n"); /* the binary handles timeouts anyway */ + "JobTimeoutSec=0\n", + WRITE_STRING_FILE_CREATE); /* the binary handles timeouts anyway */ if (r < 0) return log_error_errno(r, "Failed to write device drop-in: %m"); diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index 43aac616b6..1f3b169905 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -65,7 +65,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = write_string_file("/sys/power/resume", major_minor); + r = write_string_file("/sys/power/resume", major_minor, WRITE_STRING_FILE_CREATE); if (r < 0) { log_error_errno(r, "Failed to write '%s' to /sys/power/resume: %m", major_minor); return EXIT_FAILURE; diff --git a/src/import/pull-dkr.c b/src/import/pull-dkr.c index 78e3184c42..67ca1ce8e4 100644 --- a/src/import/pull-dkr.c +++ b/src/import/pull-dkr.c @@ -793,7 +793,7 @@ static void dkr_pull_job_on_finished_v2(PullJob *j) { } else if (i->tags_job == j) { const char *url; - _cleanup_free_ const char *buf; + _cleanup_free_ char *buf; _cleanup_json_variant_unref_ JsonVariant *doc = NULL; JsonVariant *e = NULL; diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index d9450ae8cd..9a09f401e0 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -132,7 +132,7 @@ static int request_meta_ensure_tmp(RequestMeta *m) { if (fd < 0) return fd; - m->tmp = fdopen(fd, "rw"); + m->tmp = fdopen(fd, "w+"); if (!m->tmp) { safe_close(fd); return -errno; diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index be6a5522fa..f7815b2796 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -656,13 +656,16 @@ static int journal_file_setup_field_hash_table(JournalFile *f) { return 0; } -static int journal_file_map_data_hash_table(JournalFile *f) { +int journal_file_map_data_hash_table(JournalFile *f) { uint64_t s, p; void *t; int r; assert(f); + if (f->data_hash_table) + return 0; + p = le64toh(f->header->data_hash_table_offset); s = le64toh(f->header->data_hash_table_size); @@ -678,13 +681,16 @@ static int journal_file_map_data_hash_table(JournalFile *f) { return 0; } -static int journal_file_map_field_hash_table(JournalFile *f) { +int journal_file_map_field_hash_table(JournalFile *f) { uint64_t s, p; void *t; int r; assert(f); + if (f->field_hash_table) + return 0; + p = le64toh(f->header->field_hash_table_offset); s = le64toh(f->header->field_hash_table_size); @@ -803,10 +809,18 @@ int journal_file_find_field_object_with_hash( assert(f); assert(field && size > 0); + /* If the field hash table is empty, we can't find anything */ + if (le64toh(f->header->field_hash_table_size) <= 0) + return 0; + + /* Map the field hash table, if it isn't mapped yet. */ + r = journal_file_map_field_hash_table(f); + if (r < 0) + return r; + osize = offsetof(Object, field.payload) + size; m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); - if (m <= 0) return -EBADMSG; @@ -866,6 +880,15 @@ int journal_file_find_data_object_with_hash( assert(f); assert(data || size == 0); + /* If there's no data hash table, then there's no entry. */ + if (le64toh(f->header->data_hash_table_size) <= 0) + return 0; + + /* Map the data hash table, if it isn't mapped yet. */ + r = journal_file_map_data_hash_table(f); + if (r < 0) + return r; + osize = offsetof(Object, data.payload) + size; m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); @@ -2731,14 +2754,6 @@ int journal_file_open( #endif } - r = journal_file_map_field_hash_table(f); - if (r < 0) - goto fail; - - r = journal_file_map_data_hash_table(f); - if (r < 0) - goto fail; - if (mmap_cache_got_sigbus(f->mmap, f->fd)) { r = -EIO; goto fail; diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 403c8f760c..e92b75eabe 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -229,3 +229,6 @@ int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t * int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot, usec_t *from, usec_t *to); bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec); + +int journal_file_map_data_hash_table(JournalFile *f); +int journal_file_map_field_hash_table(JournalFile *f); diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c index 81a577ea27..17499bbc30 100644 --- a/src/journal/journal-vacuum.c +++ b/src/journal/journal-vacuum.c @@ -72,7 +72,7 @@ static void patch_realtime( const struct stat *st, unsigned long long *realtime) { - _cleanup_free_ const char *path = NULL; + _cleanup_free_ char *path = NULL; usec_t x, crtime = 0; /* The timestamp was determined by the file name, but let's diff --git a/src/journal/journal-verify.c b/src/journal/journal-verify.c index ce734d8df7..eaf006db7a 100644 --- a/src/journal/journal-verify.c +++ b/src/journal/journal-verify.c @@ -69,6 +69,16 @@ static void draw_progress(uint64_t p, usec_t *last_usec) { fflush(stdout); } +static uint64_t scale_progress(uint64_t scale, uint64_t p, uint64_t m) { + + /* Calculates scale * p / m, but handles m == 0 safely, and saturates */ + + if (p >= m || m == 0) + return scale; + + return scale * p / m; +} + static void flush_progress(void) { unsigned n, i; @@ -113,8 +123,10 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o * other objects. */ if ((o->object.flags & OBJECT_COMPRESSED_XZ) && - o->object.type != OBJECT_DATA) + o->object.type != OBJECT_DATA) { + error(offset, "Found compressed object that isn't of type DATA, which is not allowed."); return -EBADMSG; + } switch (o->object.type) { @@ -123,15 +135,15 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o int compression, r; if (le64toh(o->data.entry_offset) == 0) - warning(offset, "unused data (entry_offset==0)"); + warning(offset, "Unused data (entry_offset==0)"); if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) { - error(offset, "bad n_entries: %"PRIu64, o->data.n_entries); + error(offset, "Bad n_entries: %"PRIu64, o->data.n_entries); return -EBADMSG; } if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) { - error(offset, "bad object size (<= %zu): %"PRIu64, + error(offset, "Bad object size (<= %zu): %"PRIu64, offsetof(DataObject, payload), le64toh(o->object.size)); return -EBADMSG; @@ -159,7 +171,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload)); if (h1 != h2) { - error(offset, "invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2); + error(offset, "Invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2); return -EBADMSG; } @@ -167,7 +179,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o !VALID64(o->data.next_field_offset) || !VALID64(o->data.entry_offset) || !VALID64(o->data.entry_array_offset)) { - error(offset, "invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt, + error(offset, "Invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt, o->data.next_hash_offset, o->data.next_field_offset, o->data.entry_offset, @@ -181,7 +193,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o case OBJECT_FIELD: if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) { error(offset, - "bad field size (<= %zu): %"PRIu64, + "Bad field size (<= %zu): %"PRIu64, offsetof(FieldObject, payload), le64toh(o->object.size)); return -EBADMSG; @@ -190,7 +202,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if (!VALID64(o->field.next_hash_offset) || !VALID64(o->field.head_data_offset)) { error(offset, - "invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt, + "Invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt, o->field.next_hash_offset, o->field.head_data_offset); return -EBADMSG; @@ -200,7 +212,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o case OBJECT_ENTRY: if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) { error(offset, - "bad entry size (<= %zu): %"PRIu64, + "Bad entry size (<= %zu): %"PRIu64, offsetof(EntryObject, items), le64toh(o->object.size)); return -EBADMSG; @@ -208,28 +220,28 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) { error(offset, - "invalid number items in entry: %"PRIu64, + "Invalid number items in entry: %"PRIu64, (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem)); return -EBADMSG; } if (le64toh(o->entry.seqnum) <= 0) { error(offset, - "invalid entry seqnum: %"PRIx64, + "Invalid entry seqnum: %"PRIx64, le64toh(o->entry.seqnum)); return -EBADMSG; } if (!VALID_REALTIME(le64toh(o->entry.realtime))) { error(offset, - "invalid entry realtime timestamp: %"PRIu64, + "Invalid entry realtime timestamp: %"PRIu64, le64toh(o->entry.realtime)); return -EBADMSG; } if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) { error(offset, - "invalid entry monotonic timestamp: %"PRIu64, + "Invalid entry monotonic timestamp: %"PRIu64, le64toh(o->entry.monotonic)); return -EBADMSG; } @@ -238,7 +250,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if (o->entry.items[i].object_offset == 0 || !VALID64(o->entry.items[i].object_offset)) { error(offset, - "invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt, + "Invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt, i, journal_file_entry_n_items(o), o->entry.items[i].object_offset); return -EBADMSG; @@ -252,7 +264,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 || (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) { error(offset, - "invalid %s hash table size: %"PRIu64, + "Invalid %s hash table size: %"PRIu64, o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", le64toh(o->object.size)); return -EBADMSG; @@ -262,7 +274,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if (o->hash_table.items[i].head_hash_offset != 0 && !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) { error(offset, - "invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt, + "Invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt, o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", i, journal_file_hash_table_n_items(o), le64toh(o->hash_table.items[i].head_hash_offset)); @@ -271,7 +283,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if (o->hash_table.items[i].tail_hash_offset != 0 && !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) { error(offset, - "invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt, + "Invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt, o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", i, journal_file_hash_table_n_items(o), le64toh(o->hash_table.items[i].tail_hash_offset)); @@ -281,7 +293,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if ((o->hash_table.items[i].head_hash_offset != 0) != (o->hash_table.items[i].tail_hash_offset != 0)) { error(offset, - "invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt, + "Invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt, o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field", i, journal_file_hash_table_n_items(o), le64toh(o->hash_table.items[i].head_hash_offset), @@ -296,14 +308,14 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 || (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) { error(offset, - "invalid object entry array size: %"PRIu64, + "Invalid object entry array size: %"PRIu64, le64toh(o->object.size)); return -EBADMSG; } if (!VALID64(o->entry_array.next_entry_array_offset)) { error(offset, - "invalid object entry array next_entry_array_offset: "OFSfmt, + "Invalid object entry array next_entry_array_offset: "OFSfmt, o->entry_array.next_entry_array_offset); return -EBADMSG; } @@ -312,7 +324,7 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o if (le64toh(o->entry_array.items[i]) != 0 && !VALID64(le64toh(o->entry_array.items[i]))) { error(offset, - "invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, + "Invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt, i, journal_file_entry_array_n_items(o), le64toh(o->entry_array.items[i])); return -EBADMSG; @@ -323,14 +335,14 @@ static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o case OBJECT_TAG: if (le64toh(o->object.size) != sizeof(TagObject)) { error(offset, - "invalid object tag size: %"PRIu64, + "Invalid object tag size: %"PRIu64, le64toh(o->object.size)); return -EBADMSG; } if (!VALID_EPOCH(o->tag.epoch)) { error(offset, - "invalid object tag epoch: %"PRIu64, + "Invalid object tag epoch: %"PRIu64, o->tag.epoch); return -EBADMSG; } @@ -403,8 +415,7 @@ static int entry_points_to_data( assert(entry_fd >= 0); if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) { - error(data_p, - "data object references invalid entry at "OFSfmt, entry_p); + error(data_p, "Data object references invalid entry at "OFSfmt, entry_p); return -EBADMSG; } @@ -420,8 +431,7 @@ static int entry_points_to_data( } if (!found) { - error(entry_p, - "data object at "OFSfmt" not referenced by linked entry", data_p); + error(entry_p, "Data object at "OFSfmt" not referenced by linked entry", data_p); return -EBADMSG; } @@ -464,7 +474,7 @@ static int entry_points_to_data( x = z; } - error(entry_p, "entry object doesn't exist in main entry array"); + error(entry_p, "Entry object doesn't exist in main entry array"); return -EBADMSG; } @@ -494,9 +504,7 @@ static int verify_data( /* Entry array means at least two objects */ if (a && n < 2) { - error(p, - "entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", - a, n); + error(p, "Entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")", a, n); return -EBADMSG; } @@ -516,12 +524,12 @@ static int verify_data( uint64_t next, m, j; if (a == 0) { - error(p, "array chain too short"); + error(p, "Array chain too short"); return -EBADMSG; } if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { - error(p, "invalid array offset "OFSfmt, a); + error(p, "Invalid array offset "OFSfmt, a); return -EBADMSG; } @@ -531,8 +539,7 @@ static int verify_data( next = le64toh(o->entry_array.next_entry_array_offset); if (next != 0 && next <= a) { - error(p, "array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", - a, next); + error(p, "Array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")", a, next); return -EBADMSG; } @@ -541,7 +548,7 @@ static int verify_data( q = le64toh(o->entry_array.items[j]); if (q <= last) { - error(p, "data object's entry array not sorted"); + error(p, "Data object's entry array not sorted"); return -EBADMSG; } last = q; @@ -580,11 +587,18 @@ static int verify_hash_table( assert(last_usec); n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); + if (n <= 0) + return 0; + + r = journal_file_map_data_hash_table(f); + if (r < 0) + return log_error_errno(r, "Failed to map data hash table: %m"); + for (i = 0; i < n; i++) { uint64_t last = 0, p; if (show_progress) - draw_progress(0xC000 + (0x3FFF * i / n), last_usec); + draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec); p = le64toh(f->data_hash_table[i].head_hash_offset); while (p != 0) { @@ -592,8 +606,7 @@ static int verify_hash_table( uint64_t next; if (!contains_uint64(f->mmap, data_fd, n_data, p)) { - error(p, "invalid data object at hash entry %"PRIu64" of %"PRIu64, - i, n); + error(p, "Invalid data object at hash entry %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } @@ -603,14 +616,12 @@ static int verify_hash_table( next = le64toh(o->data.next_hash_offset); if (next != 0 && next <= p) { - error(p, "hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, - i, n); + error(p, "Hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } if (le64toh(o->data.hash) % n != i) { - error(p, "hash value mismatch in hash entry %"PRIu64" of %"PRIu64, - i, n); + error(p, "Hash value mismatch in hash entry %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } @@ -623,7 +634,7 @@ static int verify_hash_table( } if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) { - error(p, "tail hash pointer mismatch in hash table"); + error(p, "Tail hash pointer mismatch in hash table"); return -EBADMSG; } } @@ -637,6 +648,13 @@ static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) assert(f); n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem); + if (n <= 0) + return 0; + + r = journal_file_map_data_hash_table(f); + if (r < 0) + return log_error_errno(r, "Failed to map data hash table: %m"); + h = hash % n; q = le64toh(f->data_hash_table[h].head_hash_offset); @@ -677,16 +695,16 @@ static int verify_entry( h = le64toh(o->entry.items[i].hash); if (!contains_uint64(f->mmap, data_fd, n_data, q)) { - error(p, "invalid data object of entry"); - return -EBADMSG; - } + error(p, "Invalid data object of entry"); + return -EBADMSG; + } r = journal_file_move_to_object(f, OBJECT_DATA, q, &u); if (r < 0) return r; if (le64toh(u->data.hash) != h) { - error(p, "hash mismatch for data object of entry"); + error(p, "Hash mismatch for data object of entry"); return -EBADMSG; } @@ -694,7 +712,7 @@ static int verify_entry( if (r < 0) return r; if (r == 0) { - error(p, "data object missing from hash table"); + error(p, "Data object missing from hash table"); return -EBADMSG; } } @@ -726,15 +744,15 @@ static int verify_entry_array( Object *o; if (show_progress) - draw_progress(0x8000 + (0x3FFF * i / n), last_usec); + draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec); if (a == 0) { - error(a, "array chain too short at %"PRIu64" of %"PRIu64, i, n); + error(a, "Array chain too short at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) { - error(a, "invalid array %"PRIu64" of %"PRIu64, i, n); + error(a, "Invalid array %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } @@ -744,9 +762,7 @@ static int verify_entry_array( next = le64toh(o->entry_array.next_entry_array_offset); if (next != 0 && next <= a) { - error(a, - "array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", - i, n, next); + error(a, "Array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")", i, n, next); return -EBADMSG; } @@ -756,15 +772,13 @@ static int verify_entry_array( p = le64toh(o->entry_array.items[j]); if (p <= last) { - error(a, "entry array not sorted at %"PRIu64" of %"PRIu64, - i, n); + error(a, "Entry array not sorted at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } last = p; if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) { - error(a, "invalid array entry at %"PRIu64" of %"PRIu64, - i, n); + error(a, "Invalid array entry at %"PRIu64" of %"PRIu64, i, n); return -EBADMSG; } @@ -852,7 +866,7 @@ int journal_file_verify( for (i = 0; i < sizeof(f->header->reserved); i++) if (f->header->reserved[i] != 0) { - error(offsetof(Header, reserved[i]), "reserved field is non-zero"); + error(offsetof(Header, reserved[i]), "Reserved field is non-zero"); r = -EBADMSG; goto fail; } @@ -861,36 +875,37 @@ int journal_file_verify( * superficial structure, headers, hashes. */ p = le64toh(f->header->header_size); - while (p != 0) { + for (;;) { + /* Early exit if there are no objects in the file, at all */ + if (le64toh(f->header->tail_object_offset) == 0) + break; + if (show_progress) - draw_progress(0x7FFF * p / le64toh(f->header->tail_object_offset), &last_usec); + draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec); r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o); if (r < 0) { - error(p, "invalid object"); + error(p, "Invalid object"); goto fail; } if (p > le64toh(f->header->tail_object_offset)) { - error(offsetof(Header, tail_object_offset), "invalid tail object pointer"); + error(offsetof(Header, tail_object_offset), "Invalid tail object pointer"); r = -EBADMSG; goto fail; } - if (p == le64toh(f->header->tail_object_offset)) - found_last = true; - n_objects ++; r = journal_file_object_verify(f, p, o); if (r < 0) { - error(p, "invalid object contents: %s", strerror(-r)); + error(p, "Envalid object contents: %s", strerror(-r)); goto fail; } if ((o->object.flags & OBJECT_COMPRESSED_XZ) && (o->object.flags & OBJECT_COMPRESSED_LZ4)) { - error(p, "objected with double compression"); + error(p, "Objected with double compression"); r = -EINVAL; goto fail; } @@ -923,7 +938,7 @@ int journal_file_verify( case OBJECT_ENTRY: if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) { - error(p, "first entry before first tag"); + error(p, "First entry before first tag"); r = -EBADMSG; goto fail; } @@ -933,21 +948,21 @@ int journal_file_verify( goto fail; if (le64toh(o->entry.realtime) < last_tag_realtime) { - error(p, "older entry after newer tag"); + error(p, "Older entry after newer tag"); r = -EBADMSG; goto fail; } if (!entry_seqnum_set && le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) { - error(p, "head entry sequence number incorrect"); + error(p, "Head entry sequence number incorrect"); r = -EBADMSG; goto fail; } if (entry_seqnum_set && entry_seqnum >= le64toh(o->entry.seqnum)) { - error(p, "entry sequence number out of synchronization"); + error(p, "Entry sequence number out of synchronization"); r = -EBADMSG; goto fail; } @@ -958,7 +973,7 @@ int journal_file_verify( if (entry_monotonic_set && sd_id128_equal(entry_boot_id, o->entry.boot_id) && entry_monotonic > le64toh(o->entry.monotonic)) { - error(p, "entry timestamp out of synchronization"); + error(p, "Entry timestamp out of synchronization"); r = -EBADMSG; goto fail; } @@ -969,7 +984,7 @@ int journal_file_verify( if (!entry_realtime_set && le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) { - error(p, "head entry realtime timestamp incorrect"); + error(p, "Head entry realtime timestamp incorrect"); r = -EBADMSG; goto fail; } @@ -982,7 +997,7 @@ int journal_file_verify( case OBJECT_DATA_HASH_TABLE: if (n_data_hash_tables > 1) { - error(p, "more than one data hash table"); + error(p, "More than one data hash table"); r = -EBADMSG; goto fail; } @@ -999,14 +1014,14 @@ int journal_file_verify( case OBJECT_FIELD_HASH_TABLE: if (n_field_hash_tables > 1) { - error(p, "more than one field hash table"); + error(p, "More than one field hash table"); r = -EBADMSG; goto fail; } if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) || le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) { - error(p, "header fields for field hash table invalid"); + error(p, "Header fields for field hash table invalid"); r = -EBADMSG; goto fail; } @@ -1021,7 +1036,7 @@ int journal_file_verify( if (p == le64toh(f->header->entry_array_offset)) { if (found_main_entry_array) { - error(p, "more than one main entry array"); + error(p, "More than one main entry array"); r = -EBADMSG; goto fail; } @@ -1034,19 +1049,19 @@ int journal_file_verify( case OBJECT_TAG: if (!JOURNAL_HEADER_SEALED(f->header)) { - error(p, "tag object in file without sealing"); + error(p, "Tag object in file without sealing"); r = -EBADMSG; goto fail; } if (le64toh(o->tag.seqnum) != n_tags + 1) { - error(p, "tag sequence number out of synchronization"); + error(p, "Tag sequence number out of synchronization"); r = -EBADMSG; goto fail; } if (le64toh(o->tag.epoch) < last_epoch) { - error(p, "epoch sequence out of synchronization"); + error(p, "Epoch sequence out of synchronization"); r = -EBADMSG; goto fail; } @@ -1055,7 +1070,7 @@ int journal_file_verify( if (f->seal) { uint64_t q, rt; - debug(p, "checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); + debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec; if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) { @@ -1102,7 +1117,7 @@ int journal_file_verify( goto fail; if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { - error(p, "tag failed verification"); + error(p, "Tag failed verification"); r = -EBADMSG; goto fail; } @@ -1124,79 +1139,69 @@ int journal_file_verify( n_weird ++; } - if (p == le64toh(f->header->tail_object_offset)) - p = 0; - else - p = p + ALIGN64(le64toh(o->object.size)); - } + if (p == le64toh(f->header->tail_object_offset)) { + found_last = true; + break; + } - if (!found_last) { - error(le64toh(f->header->tail_object_offset), "tail object pointer dead"); + p = p + ALIGN64(le64toh(o->object.size)); + }; + + if (!found_last && le64toh(f->header->tail_object_offset) != 0) { + error(le64toh(f->header->tail_object_offset), "Tail object pointer dead"); r = -EBADMSG; goto fail; } if (n_objects != le64toh(f->header->n_objects)) { - error(offsetof(Header, n_objects), "object number mismatch"); + error(offsetof(Header, n_objects), "Object number mismatch"); r = -EBADMSG; goto fail; } if (n_entries != le64toh(f->header->n_entries)) { - error(offsetof(Header, n_entries), "entry number mismatch"); + error(offsetof(Header, n_entries), "Entry number mismatch"); r = -EBADMSG; goto fail; } if (JOURNAL_HEADER_CONTAINS(f->header, n_data) && n_data != le64toh(f->header->n_data)) { - error(offsetof(Header, n_data), "data number mismatch"); + error(offsetof(Header, n_data), "Data number mismatch"); r = -EBADMSG; goto fail; } if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) && n_fields != le64toh(f->header->n_fields)) { - error(offsetof(Header, n_fields), "field number mismatch"); + error(offsetof(Header, n_fields), "Field number mismatch"); r = -EBADMSG; goto fail; } if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) && n_tags != le64toh(f->header->n_tags)) { - error(offsetof(Header, n_tags), "tag number mismatch"); + error(offsetof(Header, n_tags), "Tag number mismatch"); r = -EBADMSG; goto fail; } if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) && n_entry_arrays != le64toh(f->header->n_entry_arrays)) { - error(offsetof(Header, n_entry_arrays), "entry array number mismatch"); - r = -EBADMSG; - goto fail; - } - - if (n_data_hash_tables != 1) { - error(0, "missing data hash table"); - r = -EBADMSG; - goto fail; - } - - if (n_field_hash_tables != 1) { - error(0, "missing field hash table"); + error(offsetof(Header, n_entry_arrays), "Entry array number mismatch"); r = -EBADMSG; goto fail; } - if (!found_main_entry_array) { - error(0, "missing entry array"); + if (!found_main_entry_array && le64toh(f->header->entry_array_offset) != 0) { + error(0, "Missing entry array"); r = -EBADMSG; goto fail; } if (entry_seqnum_set && entry_seqnum != le64toh(f->header->tail_entry_seqnum)) { - error(offsetof(Header, tail_entry_seqnum), "invalid tail seqnum"); + error(offsetof(Header, tail_entry_seqnum), "Invalid tail seqnum"); r = -EBADMSG; goto fail; } @@ -1204,13 +1209,13 @@ int journal_file_verify( if (entry_monotonic_set && (!sd_id128_equal(entry_boot_id, f->header->boot_id) || entry_monotonic != le64toh(f->header->tail_entry_monotonic))) { - error(0, "invalid tail monotonic timestamp"); + error(0, "Invalid tail monotonic timestamp"); r = -EBADMSG; goto fail; } if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) { - error(0, "invalid tail realtime timestamp"); + error(0, "Invalid tail realtime timestamp"); r = -EBADMSG; goto fail; } diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 2d6ecfb750..073cc77711 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -2066,6 +2066,10 @@ int main(int argc, char *argv[]) { log_error_errno(r, "Failed to iterate through journal: %m"); goto finish; } + if (r == 0) { + printf("-- No entries --\n"); + goto finish; + } if (!arg_follow) pager_open_if_enabled(); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 46358e1c1a..28b1472ac8 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -175,9 +175,11 @@ static uint64_t available_space(Server *s, bool verbose) { fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX]; server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, - "%s journal is using %s (max allowed %s, " - "trying to leave %s free of %s available → current limit %s).", - s->system_journal ? "Permanent" : "Runtime", + "%s is currently using %s.\n" + "Maximum allowed usage is set to %s.\n" + "Leaving at least %s free (of currently available %s of space).\n" + "Enforced usage limit is thus %s.", + s->system_journal ? "Permanent journal (/var/log/journal/)" : "Runtime journal (/run/log/journal/)", format_bytes(fb1, sizeof(fb1), sum), format_bytes(fb2, sizeof(fb2), m->max_use), format_bytes(fb3, sizeof(fb3), m->keep_free), diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 9e184ac4b5..6e00b1ad30 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -72,6 +72,8 @@ struct sd_dhcp_lease { char *root_path; uint8_t *client_id; size_t client_id_len; + uint8_t *vendor_specific; + size_t vendor_specific_len; }; int dhcp_lease_new(sd_dhcp_lease **ret); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index abca9422c5..aa37e9b0b5 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -125,6 +125,7 @@ enum { DHCP_OPTION_BROADCAST = 28, DHCP_OPTION_STATIC_ROUTE = 33, DHCP_OPTION_NTP_SERVER = 42, + DHCP_OPTION_VENDOR_SPECIFIC = 43, DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, DHCP_OPTION_OVERLOAD = 52, diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index d8bc76edda..54417b3af3 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -179,6 +179,21 @@ int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routes return 0; } +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const uint8_t **data, + size_t *data_len) { + assert_return(lease, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + if (!lease->vendor_specific) + return -ENOENT; + + *data = lease->vendor_specific; + *data_len = lease->vendor_specific_len; + + return 0; +} + sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease) { if (lease) assert_se(REFCNT_INC(lease->n_ref) >= 2); @@ -194,6 +209,7 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) { free(lease->ntp); free(lease->static_route); free(lease->client_id); + free(lease->vendor_specific); free(lease); } @@ -435,7 +451,8 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const uint8_t *option, break; case DHCP_OPTION_ROUTER: - lease_parse_be32(option, len, &lease->router); + if(len >= 4) + lease_parse_be32(option, 4, &lease->router); break; @@ -579,6 +596,17 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const uint8_t *option, return r; break; + + case DHCP_OPTION_VENDOR_SPECIFIC: + if (len >= 1) { + free(lease->vendor_specific); + lease->vendor_specific = memdup(option, len); + if (!lease->vendor_specific) + return -ENOMEM; + lease->vendor_specific_len = len; + } + + break; } return 0; @@ -603,8 +631,8 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { _cleanup_fclose_ FILE *f = NULL; struct in_addr address; const struct in_addr *addresses; - const uint8_t *client_id; - size_t client_id_len; + const uint8_t *client_id, *data; + size_t client_id_len, data_len; const char *string; uint16_t mtu; struct sd_dhcp_route *routes; @@ -690,6 +718,18 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { fprintf(f, "CLIENTID=%s\n", client_id_hex); } + r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len); + if (r >= 0) { + _cleanup_free_ char *option_hex = NULL; + + option_hex = hexmem(data, data_len); + if (!option_hex) { + r = -ENOMEM; + goto finish; + } + fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); + } + r = 0; fflush(f); @@ -712,7 +752,8 @@ int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { _cleanup_free_ char *address = NULL, *router = NULL, *netmask = NULL, *server_address = NULL, *next_server = NULL, *dns = NULL, *ntp = NULL, *mtu = NULL, - *routes = NULL, *client_id_hex = NULL; + *routes = NULL, *client_id_hex = NULL, + *vendor_specific_hex = NULL; struct in_addr addr; int r; @@ -737,6 +778,7 @@ int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { "ROOT_PATH", &lease->root_path, "ROUTES", &routes, "CLIENTID", &client_id_hex, + "VENDOR_SPECIFIC", &vendor_specific_hex, NULL); if (r < 0) { if (r == -ENOENT) @@ -811,13 +853,21 @@ int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { } if (client_id_hex) { - if (strlen (client_id_hex) % 2) + if (strlen(client_id_hex) % 2) return -EINVAL; - lease->client_id = unhexmem (client_id_hex, strlen (client_id_hex)); - if (!lease->client_id) - return -ENOMEM; - lease->client_id_len = strlen (client_id_hex) / 2; + r = unhexmem(client_id_hex, strlen(client_id_hex), (void**) &lease->client_id, &lease->client_id_len); + if (r < 0) + return r; + } + + if (vendor_specific_hex) { + if (strlen(vendor_specific_hex) % 2) + return -EINVAL; + + r = unhexmem(vendor_specific_hex, strlen(vendor_specific_hex), (void**) &lease->vendor_specific, &lease->vendor_specific_len); + if (r < 0) + return r; } *ret = lease; diff --git a/src/libsystemd-terminal/.gitignore b/src/libsystemd-terminal/.gitignore deleted file mode 100644 index 7de83bd3e9..0000000000 --- a/src/libsystemd-terminal/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/unifont-glyph-array.bin diff --git a/src/libsystemd-terminal/evcat.c b/src/libsystemd-terminal/evcat.c deleted file mode 100644 index 2aeefc2e16..0000000000 --- a/src/libsystemd-terminal/evcat.c +++ /dev/null @@ -1,488 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Event Catenation - * The evcat tool catenates input events of all requested devices and prints - * them to standard-output. It's only meant for debugging of input-related - * problems. - */ - -#include <errno.h> -#include <getopt.h> -#include <libevdev/libevdev.h> -#include <linux/kd.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <termios.h> -#include <unistd.h> -#include <xkbcommon/xkbcommon.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "build.h" -#include "event-util.h" -#include "macro.h" -#include "signal-util.h" -#include "util.h" -#include "idev.h" -#include "sysview.h" -#include "term-internal.h" - -typedef struct Evcat Evcat; - -struct Evcat { - char *session; - char *seat; - sd_event *event; - sd_bus *bus; - sysview_context *sysview; - idev_context *idev; - idev_session *idev_session; - - bool managed : 1; -}; - -static Evcat *evcat_free(Evcat *e) { - if (!e) - return NULL; - - e->idev_session = idev_session_free(e->idev_session); - e->idev = idev_context_unref(e->idev); - e->sysview = sysview_context_free(e->sysview); - e->bus = sd_bus_unref(e->bus); - e->event = sd_event_unref(e->event); - free(e->seat); - free(e->session); - free(e); - - tcflush(0, TCIOFLUSH); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free); - -static bool is_managed(const char *session) { - unsigned int vtnr; - struct stat st; - long mode; - int r; - - /* Using logind's Controller API is highly fragile if there is already - * a session controller running. If it is registered as controller - * itself, TakeControl will simply fail. But if its a legacy controller - * that does not use logind's controller API, we must never register - * our own controller. Otherwise, we really mess up the VT. Therefore, - * only run in managed mode if there's no-one else. */ - - if (geteuid() == 0) - return false; - - if (!isatty(1)) - return false; - - if (!session) - return false; - - r = sd_session_get_vt(session, &vtnr); - if (r < 0 || vtnr < 1 || vtnr > 63) - return false; - - mode = 0; - r = ioctl(1, KDGETMODE, &mode); - if (r < 0 || mode != KD_TEXT) - return false; - - r = fstat(1, &st); - if (r < 0 || minor(st.st_rdev) != vtnr) - return false; - - return true; -} - -static int evcat_new(Evcat **out) { - _cleanup_(evcat_freep) Evcat *e = NULL; - int r; - - assert(out); - - e = new0(Evcat, 1); - if (!e) - return log_oom(); - - r = sd_pid_get_session(getpid(), &e->session); - if (r < 0) - return log_error_errno(r, "Cannot retrieve logind session: %m"); - - r = sd_session_get_seat(e->session, &e->seat); - if (r < 0) - return log_error_errno(r, "Cannot retrieve seat of logind session: %m"); - - e->managed = is_managed(e->session); - - r = sd_event_default(&e->event); - if (r < 0) - return r; - - r = sd_bus_open_system(&e->bus); - if (r < 0) - return r; - - r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return r; - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1); - if (r < 0) - return r; - - r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sysview_context_new(&e->sysview, - SYSVIEW_CONTEXT_SCAN_LOGIND | - SYSVIEW_CONTEXT_SCAN_EVDEV, - e->event, - e->bus, - NULL); - if (r < 0) - return r; - - r = idev_context_new(&e->idev, e->event, e->bus); - if (r < 0) - return r; - - *out = e; - e = NULL; - return 0; -} - -static void kdata_print(idev_data *data) { - idev_data_keyboard *k = &data->keyboard; - char buf[128]; - uint32_t i, c; - int cwidth; - - /* Key-press state: UP/DOWN/REPEAT */ - printf(" %-6s", k->value == 0 ? "UP" : - k->value == 1 ? "DOWN" : - "REPEAT"); - - /* Resync state */ - printf(" | %-6s", data->resync ? "RESYNC" : ""); - - /* Keycode that triggered the event */ - printf(" | %5u", (unsigned)k->keycode); - - /* Well-known name of the keycode */ - printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>"); - - /* Well-known modifiers */ - printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); - printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); - printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); - printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); - printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); - - /* Consumed modifiers */ - printf(" | %-5s", (k->consumed_mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); - printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); - printf(" %-3s", (k->consumed_mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); - printf(" %-5s", (k->consumed_mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); - printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); - - /* Resolved symbols */ - printf(" |"); - for (i = 0; i < k->n_syms; ++i) { - buf[0] = 0; - xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf)); - - if (is_locale_utf8()) { - c = k->codepoints[i]; - if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) { - /* "%4lc" doesn't work well, so hard-code it */ - cwidth = mk_wcwidth(c); - while (cwidth++ < 2) - printf(" "); - - printf(" '%lc':", (wchar_t)c); - } else { - printf(" "); - } - } - - printf(" XKB_KEY_%-30s", buf); - } - - printf("\n"); -} - -static bool kdata_is_exit(idev_data *data) { - idev_data_keyboard *k = &data->keyboard; - - if (k->value != 1) - return false; - if (k->n_syms != 1) - return false; - - return k->codepoints[0] == 'q'; -} - -static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) { - Evcat *e = userdata; - - switch (ev->type) { - case IDEV_EVENT_DEVICE_ADD: - idev_device_enable(ev->device_add.device); - break; - case IDEV_EVENT_DEVICE_REMOVE: - idev_device_disable(ev->device_remove.device); - break; - case IDEV_EVENT_DEVICE_DATA: - switch (ev->device_data.data.type) { - case IDEV_DATA_KEYBOARD: - if (kdata_is_exit(&ev->device_data.data)) - sd_event_exit(e->event, 0); - else - kdata_print(&ev->device_data.data); - - break; - } - - break; - } - - return 0; -} - -static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { - unsigned int flags, type; - Evcat *e = userdata; - sysview_device *d; - const char *name; - int r; - - switch (ev->type) { - case SYSVIEW_EVENT_SESSION_FILTER: - if (streq_ptr(e->session, ev->session_filter.id)) - return 1; - - break; - case SYSVIEW_EVENT_SESSION_ADD: - assert(!e->idev_session); - - name = sysview_session_get_name(ev->session_add.session); - flags = 0; - - if (e->managed) - flags |= IDEV_SESSION_MANAGED; - - r = idev_session_new(&e->idev_session, - e->idev, - flags, - name, - evcat_idev_fn, - e); - if (r < 0) - return log_error_errno(r, "Cannot create idev session: %m"); - - if (e->managed) { - r = sysview_session_take_control(ev->session_add.session); - if (r < 0) - return log_error_errno(r, "Cannot request session control: %m"); - } - - idev_session_enable(e->idev_session); - - break; - case SYSVIEW_EVENT_SESSION_REMOVE: - idev_session_disable(e->idev_session); - e->idev_session = idev_session_free(e->idev_session); - if (sd_event_get_exit_code(e->event, &r) == -ENODATA) - sd_event_exit(e->event, 0); - break; - case SYSVIEW_EVENT_SESSION_ATTACH: - d = ev->session_attach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_EVDEV) { - r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d)); - if (r < 0) - return log_error_errno(r, "Cannot add evdev device to idev: %m"); - } - - break; - case SYSVIEW_EVENT_SESSION_DETACH: - d = ev->session_detach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_EVDEV) { - r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d)); - if (r < 0) - return log_error_errno(r, "Cannot remove evdev device from idev: %m"); - } - - break; - case SYSVIEW_EVENT_SESSION_CONTROL: - r = ev->session_control.error; - if (r < 0) - return log_error_errno(r, "Cannot acquire session control: %m"); - - r = ioctl(1, KDSKBMODE, K_UNICODE); - if (r < 0) - return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m"); - - r = ioctl(1, KDSETMODE, KD_TEXT); - if (r < 0) - return log_error_errno(errno, "Cannot set KD_TEXT on stdout: %m"); - - printf("\n"); - - break; - } - - return 0; -} - -static int evcat_run(Evcat *e) { - struct termios in_attr, saved_attr; - int r; - - assert(e); - - if (!e->managed && geteuid() > 0) - log_warning("You run in unmanaged mode without being root. This is likely to produce no output.."); - - printf("evcat - Read and catenate events from selected input devices\n" - " Running on seat '%s' in user-session '%s'\n" - " Exit by pressing ^C or 'q'\n\n", - e->seat ? : "seat0", e->session ? : "<none>"); - - r = sysview_context_start(e->sysview, evcat_sysview_fn, e); - if (r < 0) - goto out; - - r = tcgetattr(0, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - saved_attr = in_attr; - in_attr.c_lflag &= ~ECHO; - - r = tcsetattr(0, TCSANOW, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - r = sd_event_loop(e->event); - tcsetattr(0, TCSANOW, &saved_attr); - printf("exiting..\n"); - -out: - sysview_context_stop(e->sysview); - return r; -} - -static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Read and catenate events from selected input devices.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {}, - }; - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - case 'h': - help(); - return 0; - - case ARG_VERSION: - puts(PACKAGE_STRING); - puts(SYSTEMD_FEATURES); - return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind) { - log_error("Too many arguments"); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_(evcat_freep) Evcat *e = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - setlocale(LC_ALL, ""); - if (!is_locale_utf8()) - log_warning("Locale is not set to UTF-8. Codepoints will not be printed!"); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = evcat_new(&e); - if (r < 0) - goto finish; - - r = evcat_run(e); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c deleted file mode 100644 index 10c13e348a..0000000000 --- a/src/libsystemd-terminal/grdev-drm.c +++ /dev/null @@ -1,3092 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <fcntl.h> -#include <inttypes.h> -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/types.h> -#include <unistd.h> - -/* Yuck! DRM headers need system headers included first.. but we have to - * include it before util/missing.h to avoid redefining ioctl bits */ -#include <drm.h> -#include <drm_fourcc.h> -#include <drm_mode.h> - -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "macro.h" -#include "util.h" -#include "bus-util.h" -#include "grdev.h" -#include "grdev-internal.h" - -#define GRDRM_MAX_TRIES (16) - -typedef struct grdrm_object grdrm_object; -typedef struct grdrm_plane grdrm_plane; -typedef struct grdrm_connector grdrm_connector; -typedef struct grdrm_encoder grdrm_encoder; -typedef struct grdrm_crtc grdrm_crtc; - -typedef struct grdrm_fb grdrm_fb; -typedef struct grdrm_pipe grdrm_pipe; -typedef struct grdrm_card grdrm_card; -typedef struct unmanaged_card unmanaged_card; -typedef struct managed_card managed_card; - -/* - * Objects - */ - -enum { - GRDRM_TYPE_CRTC, - GRDRM_TYPE_ENCODER, - GRDRM_TYPE_CONNECTOR, - GRDRM_TYPE_PLANE, - GRDRM_TYPE_CNT -}; - -struct grdrm_object { - grdrm_card *card; - uint32_t id; - uint32_t index; - unsigned int type; - void (*free_fn) (grdrm_object *object); - - bool present : 1; - bool assigned : 1; -}; - -struct grdrm_plane { - grdrm_object object; - - struct { - uint32_t used_crtc; - uint32_t used_fb; - uint32_t gamma_size; - - uint32_t n_crtcs; - uint32_t max_crtcs; - uint32_t *crtcs; - uint32_t n_formats; - uint32_t max_formats; - uint32_t *formats; - } kern; -}; - -struct grdrm_connector { - grdrm_object object; - - struct { - uint32_t type; - uint32_t type_id; - uint32_t used_encoder; - uint32_t connection; - uint32_t mm_width; - uint32_t mm_height; - uint32_t subpixel; - - uint32_t n_encoders; - uint32_t max_encoders; - uint32_t *encoders; - uint32_t n_modes; - uint32_t max_modes; - struct drm_mode_modeinfo *modes; - uint32_t n_props; - uint32_t max_props; - uint32_t *prop_ids; - uint64_t *prop_values; - } kern; -}; - -struct grdrm_encoder { - grdrm_object object; - - struct { - uint32_t type; - uint32_t used_crtc; - - uint32_t n_crtcs; - uint32_t max_crtcs; - uint32_t *crtcs; - uint32_t n_clones; - uint32_t max_clones; - uint32_t *clones; - } kern; -}; - -struct grdrm_crtc { - grdrm_object object; - - struct { - uint32_t used_fb; - uint32_t fb_offset_x; - uint32_t fb_offset_y; - uint32_t gamma_size; - - uint32_t n_used_connectors; - uint32_t max_used_connectors; - uint32_t *used_connectors; - - bool mode_set; - struct drm_mode_modeinfo mode; - } kern; - - struct { - bool set; - uint32_t fb; - uint32_t fb_x; - uint32_t fb_y; - uint32_t gamma; - - uint32_t n_connectors; - uint32_t *connectors; - - bool mode_set; - struct drm_mode_modeinfo mode; - } old; - - struct { - struct drm_mode_modeinfo mode; - uint32_t n_connectors; - uint32_t max_connectors; - uint32_t *connectors; - } set; - - grdrm_pipe *pipe; - - bool applied : 1; -}; - -#define GRDRM_OBJECT_INIT(_card, _id, _index, _type, _free_fn) ((grdrm_object){ \ - .card = (_card), \ - .id = (_id), \ - .index = (_index), \ - .type = (_type), \ - .free_fn = (_free_fn), \ - }) - -grdrm_object *grdrm_find_object(grdrm_card *card, uint32_t id); -int grdrm_object_add(grdrm_object *object); -grdrm_object *grdrm_object_free(grdrm_object *object); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdrm_object*, grdrm_object_free); - -int grdrm_plane_new(grdrm_plane **out, grdrm_card *card, uint32_t id, uint32_t index); -int grdrm_connector_new(grdrm_connector **out, grdrm_card *card, uint32_t id, uint32_t index); -int grdrm_encoder_new(grdrm_encoder **out, grdrm_card *card, uint32_t id, uint32_t index); -int grdrm_crtc_new(grdrm_crtc **out, grdrm_card *card, uint32_t id, uint32_t index); - -#define plane_from_object(_obj) container_of((_obj), grdrm_plane, object) -#define connector_from_object(_obj) container_of((_obj), grdrm_connector, object) -#define encoder_from_object(_obj) container_of((_obj), grdrm_encoder, object) -#define crtc_from_object(_obj) container_of((_obj), grdrm_crtc, object) - -/* - * Framebuffers - */ - -struct grdrm_fb { - grdev_fb base; - grdrm_card *card; - uint32_t id; - uint32_t handles[4]; - uint32_t offsets[4]; - uint32_t sizes[4]; - uint32_t flipid; -}; - -static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_modeinfo *mode); -grdrm_fb *grdrm_fb_free(grdrm_fb *fb); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdrm_fb*, grdrm_fb_free); - -#define fb_from_base(_fb) container_of((_fb), grdrm_fb, base) - -/* - * Pipes - */ - -struct grdrm_pipe { - grdev_pipe base; - grdrm_crtc *crtc; - uint32_t counter; -}; - -#define grdrm_pipe_from_base(_e) container_of((_e), grdrm_pipe, base) - -#define GRDRM_PIPE_NAME_MAX (GRDRM_CARD_NAME_MAX + 1 + DECIMAL_STR_MAX(uint32_t)) - -static const grdev_pipe_vtable grdrm_pipe_vtable; - -static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_modeinfo *mode, size_t n_fbs); - -/* - * Cards - */ - -struct grdrm_card { - grdev_card base; - - int fd; - sd_event_source *fd_src; - - uint32_t n_crtcs; - uint32_t n_encoders; - uint32_t n_connectors; - uint32_t n_planes; - uint32_t max_ids; - Hashmap *object_map; - - bool async_hotplug : 1; - bool hotplug : 1; - bool running : 1; - bool ready : 1; - bool cap_dumb : 1; - bool cap_monotonic : 1; -}; - -struct unmanaged_card { - grdrm_card card; - char *devnode; -}; - -struct managed_card { - grdrm_card card; - dev_t devnum; - - sd_bus_slot *slot_pause_device; - sd_bus_slot *slot_resume_device; - sd_bus_slot *slot_take_device; - - bool requested : 1; /* TakeDevice() was sent */ - bool acquired : 1; /* TakeDevice() was successful */ - bool master : 1; /* we are DRM-Master */ -}; - -#define grdrm_card_from_base(_e) container_of((_e), grdrm_card, base) -#define unmanaged_card_from_base(_e) \ - container_of(grdrm_card_from_base(_e), unmanaged_card, card) -#define managed_card_from_base(_e) \ - container_of(grdrm_card_from_base(_e), managed_card, card) - -#define GRDRM_CARD_INIT(_vtable, _session) ((grdrm_card){ \ - .base = GRDEV_CARD_INIT((_vtable), (_session)), \ - .fd = -1, \ - .max_ids = 32, \ - }) - -#define GRDRM_CARD_NAME_MAX (6 + DECIMAL_STR_MAX(unsigned) * 2) - -static const grdev_card_vtable unmanaged_card_vtable; -static const grdev_card_vtable managed_card_vtable; - -static int grdrm_card_open(grdrm_card *card, int dev_fd); -static void grdrm_card_close(grdrm_card *card); -static bool grdrm_card_async(grdrm_card *card, int r); - -/* - * The page-flip event of the kernel provides 64bit of arbitrary user-data. As - * drivers tend to drop events on intermediate deep mode-sets or because we - * might receive events during session activation, we try to avoid allocaing - * dynamic data on those events. Instead, we safe the CRTC id plus a 32bit - * counter in there. This way, we only get 32bit counters, not 64bit, but that - * should be more than enough. On the bright side, we no longer care whether we - * lose events. No memory leaks will occur. - * Modern DRM drivers might be fixed to no longer leak events, but we want to - * be safe. And associating dynamically allocated data with those events is - * kinda ugly, anyway. - */ - -static uint64_t grdrm_encode_vblank_data(uint32_t id, uint32_t counter) { - return id | ((uint64_t)counter << 32); -} - -static void grdrm_decode_vblank_data(uint64_t data, uint32_t *out_id, uint32_t *out_counter) { - if (out_id) - *out_id = data & 0xffffffffU; - if (out_counter) - *out_counter = (data >> 32) & 0xffffffffU; -} - -static bool grdrm_modes_compatible(const struct drm_mode_modeinfo *a, const struct drm_mode_modeinfo *b) { - assert(a); - assert(b); - - /* Test whether both modes are compatible according to our internal - * assumptions on modes. This comparison is highly dependent on how - * we treat modes in grdrm. If we export mode details, we need to - * make this comparison much stricter. */ - - if (a->hdisplay != b->hdisplay) - return false; - if (a->vdisplay != b->vdisplay) - return false; - if (a->vrefresh != b->vrefresh) - return false; - - return true; -} - -/* - * Objects - */ - -grdrm_object *grdrm_find_object(grdrm_card *card, uint32_t id) { - assert_return(card, NULL); - - return id > 0 ? hashmap_get(card->object_map, UINT32_TO_PTR(id)) : NULL; -} - -int grdrm_object_add(grdrm_object *object) { - int r; - - assert(object); - assert(object->card); - assert(object->id > 0); - assert(IN_SET(object->type, GRDRM_TYPE_CRTC, GRDRM_TYPE_ENCODER, GRDRM_TYPE_CONNECTOR, GRDRM_TYPE_PLANE)); - assert(object->free_fn); - - if (object->index >= 32) - log_debug("grdrm: %s: object index exceeds 32bit masks: type=%u, index=%" PRIu32, - object->card->base.name, object->type, object->index); - - r = hashmap_put(object->card->object_map, UINT32_TO_PTR(object->id), object); - if (r < 0) - return r; - - return 0; -} - -grdrm_object *grdrm_object_free(grdrm_object *object) { - if (!object) - return NULL; - - assert(object->card); - assert(object->id > 0); - assert(IN_SET(object->type, GRDRM_TYPE_CRTC, GRDRM_TYPE_ENCODER, GRDRM_TYPE_CONNECTOR, GRDRM_TYPE_PLANE)); - assert(object->free_fn); - - hashmap_remove_value(object->card->object_map, UINT32_TO_PTR(object->id), object); - - object->free_fn(object); - return NULL; -} - -/* - * Planes - */ - -static void plane_free(grdrm_object *object) { - grdrm_plane *plane = plane_from_object(object); - - free(plane->kern.formats); - free(plane->kern.crtcs); - free(plane); -} - -int grdrm_plane_new(grdrm_plane **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_plane *plane; - int r; - - assert(card); - - plane = new0(grdrm_plane, 1); - if (!plane) - return -ENOMEM; - - object = &plane->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_PLANE, plane_free); - - plane->kern.max_crtcs = 32; - plane->kern.crtcs = new0(uint32_t, plane->kern.max_crtcs); - if (!plane->kern.crtcs) - return -ENOMEM; - - plane->kern.max_formats = 32; - plane->kern.formats = new0(uint32_t, plane->kern.max_formats); - if (!plane->kern.formats) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = plane; - object = NULL; - return 0; -} - -static int grdrm_plane_resync(grdrm_plane *plane) { - grdrm_card *card = plane->object.card; - size_t tries; - int r; - - assert(plane); - - for (tries = 0; tries < GRDRM_MAX_TRIES; ++tries) { - struct drm_mode_get_plane res; - grdrm_object *object; - bool resized = false; - Iterator iter; - - zero(res); - res.plane_id = plane->object.id; - res.format_type_ptr = PTR_TO_UINT64(plane->kern.formats); - res.count_format_types = plane->kern.max_formats; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETPLANE, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: plane %u removed during resync", - card->base.name, plane->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve plane %u: %m", - card->base.name, plane->object.id); - } - - return r; - } - - plane->kern.n_crtcs = 0; - memzero(plane->kern.crtcs, sizeof(uint32_t) * plane->kern.max_crtcs); - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CRTC || object->index >= 32) - continue; - if (!(res.possible_crtcs & (1 << object->index))) - continue; - if (plane->kern.n_crtcs >= 32) { - log_debug("grdrm: %s: possible_crtcs of plane %" PRIu32 " exceeds 32bit mask", - card->base.name, plane->object.id); - continue; - } - - plane->kern.crtcs[plane->kern.n_crtcs++] = object->id; - } - - if (res.count_format_types > plane->kern.max_formats) { - uint32_t max, *t; - - max = ALIGN_POWER2(res.count_format_types); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive plane resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - t = realloc(plane->kern.formats, sizeof(*t) * max); - if (!t) - return -ENOMEM; - - plane->kern.formats = t; - plane->kern.max_formats = max; - resized = true; - } - - if (resized) - continue; - - plane->kern.n_formats = res.count_format_types; - plane->kern.used_crtc = res.crtc_id; - plane->kern.used_fb = res.fb_id; - plane->kern.gamma_size = res.gamma_size; - - break; - } - - if (tries >= GRDRM_MAX_TRIES) { - log_debug("grdrm: %s: plane %u not settled for retrieval", card->base.name, plane->object.id); - return -EFAULT; - } - - return 0; -} - -/* - * Connectors - */ - -static void connector_free(grdrm_object *object) { - grdrm_connector *connector = connector_from_object(object); - - free(connector->kern.prop_values); - free(connector->kern.prop_ids); - free(connector->kern.modes); - free(connector->kern.encoders); - free(connector); -} - -int grdrm_connector_new(grdrm_connector **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_connector *connector; - int r; - - assert(card); - - connector = new0(grdrm_connector, 1); - if (!connector) - return -ENOMEM; - - object = &connector->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_CONNECTOR, connector_free); - - connector->kern.max_encoders = 32; - connector->kern.encoders = new0(uint32_t, connector->kern.max_encoders); - if (!connector->kern.encoders) - return -ENOMEM; - - connector->kern.max_modes = 32; - connector->kern.modes = new0(struct drm_mode_modeinfo, connector->kern.max_modes); - if (!connector->kern.modes) - return -ENOMEM; - - connector->kern.max_props = 32; - connector->kern.prop_ids = new0(uint32_t, connector->kern.max_props); - connector->kern.prop_values = new0(uint64_t, connector->kern.max_props); - if (!connector->kern.prop_ids || !connector->kern.prop_values) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = connector; - object = NULL; - return 0; -} - -static int grdrm_connector_resync(grdrm_connector *connector) { - grdrm_card *card = connector->object.card; - size_t tries; - int r; - - assert(connector); - - for (tries = 0; tries < GRDRM_MAX_TRIES; ++tries) { - struct drm_mode_get_connector res; - bool resized = false; - uint32_t max; - - zero(res); - res.connector_id = connector->object.id; - res.encoders_ptr = PTR_TO_UINT64(connector->kern.encoders); - res.props_ptr = PTR_TO_UINT64(connector->kern.prop_ids); - res.prop_values_ptr = PTR_TO_UINT64(connector->kern.prop_values); - res.count_encoders = connector->kern.max_encoders; - res.count_props = connector->kern.max_props; - - /* The kernel reads modes from the EDID information only if we - * pass count_modes==0. This is a legacy hack for libdrm (which - * called every ioctl twice). Now we have to adopt.. *sigh*. - * If we never received an hotplug event, there's no reason to - * sync modes. EDID reads are heavy, so skip that if not - * required. */ - if (card->hotplug) { - if (tries > 0) { - res.modes_ptr = PTR_TO_UINT64(connector->kern.modes); - res.count_modes = connector->kern.max_modes; - } else { - resized = true; - } - } - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETCONNECTOR, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: connector %u removed during resync", - card->base.name, connector->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve connector %u: %m", - card->base.name, connector->object.id); - } - - return r; - } - - if (res.count_encoders > connector->kern.max_encoders) { - uint32_t *t; - - max = ALIGN_POWER2(res.count_encoders); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive connector resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - t = realloc(connector->kern.encoders, sizeof(*t) * max); - if (!t) - return -ENOMEM; - - connector->kern.encoders = t; - connector->kern.max_encoders = max; - resized = true; - } - - if (res.count_modes > connector->kern.max_modes) { - struct drm_mode_modeinfo *t; - - max = ALIGN_POWER2(res.count_modes); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive connector resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - t = realloc(connector->kern.modes, sizeof(*t) * max); - if (!t) - return -ENOMEM; - - connector->kern.modes = t; - connector->kern.max_modes = max; - resized = true; - } - - if (res.count_props > connector->kern.max_props) { - uint32_t *tids; - uint64_t *tvals; - - max = ALIGN_POWER2(res.count_props); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive connector resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - tids = realloc(connector->kern.prop_ids, sizeof(*tids) * max); - if (!tids) - return -ENOMEM; - connector->kern.prop_ids = tids; - - tvals = realloc(connector->kern.prop_values, sizeof(*tvals) * max); - if (!tvals) - return -ENOMEM; - connector->kern.prop_values = tvals; - - connector->kern.max_props = max; - resized = true; - } - - if (resized) - continue; - - connector->kern.n_encoders = res.count_encoders; - connector->kern.n_props = res.count_props; - connector->kern.type = res.connector_type; - connector->kern.type_id = res.connector_type_id; - connector->kern.used_encoder = res.encoder_id; - connector->kern.connection = res.connection; - connector->kern.mm_width = res.mm_width; - connector->kern.mm_height = res.mm_height; - connector->kern.subpixel = res.subpixel; - if (res.modes_ptr == PTR_TO_UINT64(connector->kern.modes)) - connector->kern.n_modes = res.count_modes; - - break; - } - - if (tries >= GRDRM_MAX_TRIES) { - log_debug("grdrm: %s: connector %u not settled for retrieval", card->base.name, connector->object.id); - return -EFAULT; - } - - return 0; -} - -/* - * Encoders - */ - -static void encoder_free(grdrm_object *object) { - grdrm_encoder *encoder = encoder_from_object(object); - - free(encoder->kern.clones); - free(encoder->kern.crtcs); - free(encoder); -} - -int grdrm_encoder_new(grdrm_encoder **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_encoder *encoder; - int r; - - assert(card); - - encoder = new0(grdrm_encoder, 1); - if (!encoder) - return -ENOMEM; - - object = &encoder->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_ENCODER, encoder_free); - - encoder->kern.max_crtcs = 32; - encoder->kern.crtcs = new0(uint32_t, encoder->kern.max_crtcs); - if (!encoder->kern.crtcs) - return -ENOMEM; - - encoder->kern.max_clones = 32; - encoder->kern.clones = new0(uint32_t, encoder->kern.max_clones); - if (!encoder->kern.clones) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = encoder; - object = NULL; - return 0; -} - -static int grdrm_encoder_resync(grdrm_encoder *encoder) { - grdrm_card *card = encoder->object.card; - struct drm_mode_get_encoder res; - grdrm_object *object; - Iterator iter; - int r; - - assert(encoder); - - zero(res); - res.encoder_id = encoder->object.id; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETENCODER, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: encoder %u removed during resync", - card->base.name, encoder->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve encoder %u: %m", - card->base.name, encoder->object.id); - } - - return r; - } - - encoder->kern.type = res.encoder_type; - encoder->kern.used_crtc = res.crtc_id; - - encoder->kern.n_crtcs = 0; - memzero(encoder->kern.crtcs, sizeof(uint32_t) * encoder->kern.max_crtcs); - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CRTC || object->index >= 32) - continue; - if (!(res.possible_crtcs & (1 << object->index))) - continue; - if (encoder->kern.n_crtcs >= 32) { - log_debug("grdrm: %s: possible_crtcs exceeds 32bit mask", card->base.name); - continue; - } - - encoder->kern.crtcs[encoder->kern.n_crtcs++] = object->id; - } - - encoder->kern.n_clones = 0; - memzero(encoder->kern.clones, sizeof(uint32_t) * encoder->kern.max_clones); - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_ENCODER || object->index >= 32) - continue; - if (!(res.possible_clones & (1 << object->index))) - continue; - if (encoder->kern.n_clones >= 32) { - log_debug("grdrm: %s: possible_encoders exceeds 32bit mask", card->base.name); - continue; - } - - encoder->kern.clones[encoder->kern.n_clones++] = object->id; - } - - return 0; -} - -/* - * Crtcs - */ - -static void crtc_free(grdrm_object *object) { - grdrm_crtc *crtc = crtc_from_object(object); - - if (crtc->pipe) - grdev_pipe_free(&crtc->pipe->base); - free(crtc->set.connectors); - free(crtc->old.connectors); - free(crtc->kern.used_connectors); - free(crtc); -} - -int grdrm_crtc_new(grdrm_crtc **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_crtc *crtc; - int r; - - assert(card); - - crtc = new0(grdrm_crtc, 1); - if (!crtc) - return -ENOMEM; - - object = &crtc->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_CRTC, crtc_free); - - crtc->kern.max_used_connectors = 32; - crtc->kern.used_connectors = new0(uint32_t, crtc->kern.max_used_connectors); - if (!crtc->kern.used_connectors) - return -ENOMEM; - - crtc->old.connectors = new0(uint32_t, crtc->kern.max_used_connectors); - if (!crtc->old.connectors) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = crtc; - object = NULL; - return 0; -} - -static int grdrm_crtc_resync(grdrm_crtc *crtc) { - grdrm_card *card = crtc->object.card; - struct drm_mode_crtc res = { .crtc_id = crtc->object.id }; - int r; - - assert(crtc); - - /* make sure we can cache any combination later */ - if (card->n_connectors > crtc->kern.max_used_connectors) { - uint32_t max, *t; - - max = ALIGN_POWER2(card->n_connectors); - if (!max) - return -ENOMEM; - - t = realloc_multiply(crtc->kern.used_connectors, sizeof(*t), max); - if (!t) - return -ENOMEM; - - crtc->kern.used_connectors = t; - crtc->kern.max_used_connectors = max; - - if (!crtc->old.set) { - crtc->old.connectors = calloc(sizeof(*t), max); - if (!crtc->old.connectors) - return -ENOMEM; - } - } - - /* GETCRTC doesn't return connectors. We have to read all - * encoder-state and deduce the setup ourselves.. */ - crtc->kern.n_used_connectors = 0; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETCRTC, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: crtc %u removed during resync", - card->base.name, crtc->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve crtc %u: %m", - card->base.name, crtc->object.id); - } - - return r; - } - - crtc->kern.used_fb = res.fb_id; - crtc->kern.fb_offset_x = res.x; - crtc->kern.fb_offset_y = res.y; - crtc->kern.gamma_size = res.gamma_size; - crtc->kern.mode_set = res.mode_valid; - crtc->kern.mode = res.mode; - - return 0; -} - -static void grdrm_crtc_assign(grdrm_crtc *crtc, grdrm_connector *connector) { - uint32_t n_connectors; - int r; - - assert(crtc); - assert(!crtc->object.assigned); - assert(!connector || !connector->object.assigned); - - /* always mark both as assigned; even if assignments cannot be set */ - crtc->object.assigned = true; - if (connector) - connector->object.assigned = true; - - /* we will support hw clone mode in the future */ - n_connectors = connector ? 1 : 0; - - /* bail out if configuration is preserved */ - if (crtc->set.n_connectors == n_connectors && - (n_connectors == 0 || crtc->set.connectors[0] == connector->object.id)) - return; - - crtc->applied = false; - crtc->set.n_connectors = 0; - - if (n_connectors > crtc->set.max_connectors) { - uint32_t max, *t; - - max = ALIGN_POWER2(n_connectors); - if (!max) { - r = -ENOMEM; - goto error; - } - - t = realloc(crtc->set.connectors, sizeof(*t) * max); - if (!t) { - r = -ENOMEM; - goto error; - } - - crtc->set.connectors = t; - crtc->set.max_connectors = max; - } - - if (connector) { - struct drm_mode_modeinfo *m, *pref = NULL; - uint32_t i; - - for (i = 0; i < connector->kern.n_modes; ++i) { - m = &connector->kern.modes[i]; - - /* ignore 3D modes by default */ - if (m->flags & DRM_MODE_FLAG_3D_MASK) - continue; - - if (!pref) { - pref = m; - continue; - } - - /* use PREFERRED over non-PREFERRED */ - if ((pref->type & DRM_MODE_TYPE_PREFERRED) && - !(m->type & DRM_MODE_TYPE_PREFERRED)) - continue; - - /* use DRIVER over non-PREFERRED|DRIVER */ - if ((pref->type & DRM_MODE_TYPE_DRIVER) && - !(m->type & (DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED))) - continue; - - /* always prefer higher resolution */ - if (pref->hdisplay > m->hdisplay || - (pref->hdisplay == m->hdisplay && pref->vdisplay > m->vdisplay)) - continue; - - pref = m; - } - - if (pref) { - crtc->set.mode = *pref; - crtc->set.n_connectors = 1; - crtc->set.connectors[0] = connector->object.id; - log_debug("grdrm: %s: assigned connector %" PRIu32 " to crtc %" PRIu32 " with mode %s", - crtc->object.card->base.name, connector->object.id, crtc->object.id, pref->name); - } else { - log_debug("grdrm: %s: connector %" PRIu32 " to be assigned but has no valid mode", - crtc->object.card->base.name, connector->object.id); - } - } - - return; - -error: - log_debug("grdrm: %s: cannot assign crtc %" PRIu32 ": %s", - crtc->object.card->base.name, crtc->object.id, strerror(-r)); -} - -static void grdrm_crtc_expose(grdrm_crtc *crtc) { - grdrm_pipe *pipe; - grdrm_fb *fb; - size_t i; - int r; - - assert(crtc); - assert(crtc->object.assigned); - - if (crtc->set.n_connectors < 1) { - if (crtc->pipe) - grdev_pipe_free(&crtc->pipe->base); - crtc->pipe = NULL; - return; - } - - pipe = crtc->pipe; - if (pipe) { - if (pipe->base.width != crtc->set.mode.hdisplay || - pipe->base.height != crtc->set.mode.vdisplay || - pipe->base.vrefresh != crtc->set.mode.vrefresh) { - grdev_pipe_free(&pipe->base); - crtc->pipe = NULL; - pipe = NULL; - } - } - - if (crtc->pipe) { - pipe->base.front = NULL; - pipe->base.back = NULL; - for (i = 0; i < pipe->base.max_fbs; ++i) { - fb = fb_from_base(pipe->base.fbs[i]); - if (fb->id == crtc->kern.used_fb) - pipe->base.front = &fb->base; - else if (!fb->flipid) - pipe->base.back = &fb->base; - } - } else { - r = grdrm_pipe_new(&pipe, crtc, &crtc->set.mode, 2); - if (r < 0) { - log_debug("grdrm: %s: cannot create pipe for crtc %" PRIu32 ": %s", - crtc->object.card->base.name, crtc->object.id, strerror(-r)); - return; - } - - for (i = 0; i < pipe->base.max_fbs; ++i) { - r = grdrm_fb_new(&fb, crtc->object.card, &crtc->set.mode); - if (r < 0) { - log_debug("grdrm: %s: cannot allocate framebuffer for crtc %" PRIu32 ": %s", - crtc->object.card->base.name, crtc->object.id, strerror(-r)); - grdev_pipe_free(&pipe->base); - return; - } - - pipe->base.fbs[i] = &fb->base; - } - - pipe->base.front = NULL; - pipe->base.back = pipe->base.fbs[0]; - crtc->pipe = pipe; - } - - grdev_pipe_ready(&crtc->pipe->base, true); -} - -static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb *basefb) { - struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - grdrm_pipe *pipe = crtc->pipe; - grdrm_fb *fb; - int r; - - assert(crtc); - assert(basefb); - assert(pipe); - - fb = fb_from_base(basefb); - - set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->set.connectors); - set_crtc.count_connectors = crtc->set.n_connectors; - set_crtc.fb_id = fb->id; - set_crtc.x = 0; - set_crtc.y = 0; - set_crtc.mode_valid = 1; - set_crtc.mode = crtc->set.mode; - - r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot set crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - grdrm_card_async(card, r); - return; - } - - if (!crtc->applied) { - log_debug("grdrm: %s: crtc %" PRIu32 " applied via deep modeset", - card->base.name, crtc->object.id); - crtc->applied = true; - } - - pipe->base.back = NULL; - pipe->base.front = &fb->base; - fb->flipid = 0; - ++pipe->counter; - pipe->base.flipping = false; - pipe->base.flip = false; - - /* We cannot schedule dummy page-flips on pipes, hence, the - * application would have to schedule their own frame-timers. - * To avoid duplicating that everywhere, we schedule our own - * timer and raise a fake FRAME event when it fires. */ - grdev_pipe_schedule(&pipe->base, 1); -} - -static int grdrm_crtc_commit_flip(grdrm_crtc *crtc, grdev_fb *basefb) { - struct drm_mode_crtc_page_flip page_flip = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - grdrm_pipe *pipe = crtc->pipe; - grdrm_fb *fb; - uint32_t cnt; - int r; - - assert(crtc); - assert(basefb); - assert(pipe); - - if (!crtc->applied) { - if (!grdrm_modes_compatible(&crtc->kern.mode, &crtc->set.mode)) - return 0; - - /* TODO: Theoretically, we should be able to page-flip to our - * framebuffer here. We didn't perform any deep modeset, but the - * DRM driver is really supposed to reject our page-flip in case - * the FB is not compatible. We then properly fall back to a - * deep modeset. - * As it turns out, drivers don't to this. Therefore, we need to - * perform a full modeset on enter now. We might avoid this in - * the future with fixed drivers.. */ - - return 0; - } - - fb = fb_from_base(basefb); - - cnt = ++pipe->counter ? : ++pipe->counter; - page_flip.fb_id = fb->id; - page_flip.flags = DRM_MODE_PAGE_FLIP_EVENT; - page_flip.user_data = grdrm_encode_vblank_data(crtc->object.id, cnt); - - r = ioctl(card->fd, DRM_IOCTL_MODE_PAGE_FLIP, &page_flip); - if (r < 0) { - r = -errno; - /* Avoid excessive logging on EINVAL; it is currently not - * possible to see whether cards support page-flipping, so - * avoid logging on each frame. */ - if (r != -EINVAL) - log_debug_errno(errno, "grdrm: %s: cannot schedule page-flip on crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - if (grdrm_card_async(card, r)) - return r; - - return 0; - } - - if (!crtc->applied) { - log_debug("grdrm: %s: crtc %" PRIu32 " applied via page flip", - card->base.name, crtc->object.id); - crtc->applied = true; - } - - pipe->base.flipping = true; - pipe->base.flip = false; - pipe->counter = cnt; - fb->flipid = cnt; - pipe->base.back = NULL; - - /* Raise fake FRAME event if it takes longer than 2 - * frames to receive the pageflip event. We assume the - * queue ran over or some other error happened. */ - grdev_pipe_schedule(&pipe->base, 2); - - return 1; -} - -static void grdrm_crtc_commit(grdrm_crtc *crtc) { - struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - grdrm_pipe *pipe; - grdev_fb *fb; - int r; - - assert(crtc); - assert(crtc->object.assigned); - - pipe = crtc->pipe; - if (!pipe) { - /* If a crtc is not assigned any connector, we want any - * previous setup to be cleared, so make sure the CRTC is - * disabled. Otherwise, there might be content on the CRTC - * while we run, which is not what we want. - * If you want to avoid modesets on specific CRTCs, you should - * still keep their assignment, but never enable the resulting - * pipe. This way, we wouldn't touch it at all. */ - if (!crtc->applied) { - crtc->applied = true; - r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot shutdown crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - grdrm_card_async(card, r); - return; - } - - log_debug("grdrm: %s: crtc %" PRIu32 " applied via shutdown", - card->base.name, crtc->object.id); - } - - return; - } - - /* we always fully ignore disabled pipes */ - if (!pipe->base.enabled) - return; - - assert(crtc->set.n_connectors > 0); - - if (pipe->base.flip) - fb = pipe->base.back; - else if (!crtc->applied) - fb = pipe->base.front; - else - return; - - if (!fb) - return; - - r = grdrm_crtc_commit_flip(crtc, fb); - if (r == 0) { - /* in case we couldn't page-flip, perform deep modeset */ - grdrm_crtc_commit_deep(crtc, fb); - } -} - -static void grdrm_crtc_restore(grdrm_crtc *crtc) { - struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - int r; - - if (!crtc->old.set) - return; - - set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->old.connectors); - set_crtc.count_connectors = crtc->old.n_connectors; - set_crtc.fb_id = crtc->old.fb; - set_crtc.x = crtc->old.fb_x; - set_crtc.y = crtc->old.fb_y; - set_crtc.gamma_size = crtc->old.gamma; - set_crtc.mode_valid = crtc->old.mode_set; - set_crtc.mode = crtc->old.mode; - - r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot restore crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - grdrm_card_async(card, r); - return; - } - - if (crtc->pipe) { - ++crtc->pipe->counter; - crtc->pipe->base.front = NULL; - crtc->pipe->base.flipping = false; - } - - log_debug("grdrm: %s: crtc %" PRIu32 " restored", card->base.name, crtc->object.id); -} - -static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct drm_event_vblank *event) { - bool flipped = false; - grdrm_pipe *pipe; - size_t i; - - assert(crtc); - assert(event); - - pipe = crtc->pipe; - if (!pipe) - return; - - /* We got a page-flip event. To be safe, we reset all FBs on the same - * pipe that have smaller flipids than the flip we got as we know they - * are executed in order. We need to do this to guarantee - * queue-overflows or other missed events don't cause starvation. - * Furthermore, if we find the exact FB this event is for, *and* this - * is the most recent event, we mark it as front FB and raise a - * frame event. */ - - for (i = 0; i < pipe->base.max_fbs; ++i) { - grdrm_fb *fb; - - if (!pipe->base.fbs[i]) - continue; - - fb = fb_from_base(pipe->base.fbs[i]); - if (counter != 0 && counter == pipe->counter && fb->flipid == counter) { - pipe->base.front = &fb->base; - fb->flipid = 0; - flipped = true; - } else if (counter - fb->flipid < UINT16_MAX) { - fb->flipid = 0; - } - } - - if (flipped) { - crtc->pipe->base.flipping = false; - grdev_pipe_frame(&pipe->base); - } -} - -/* - * Framebuffers - */ - -static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_modeinfo *mode) { - _cleanup_(grdrm_fb_freep) grdrm_fb *fb = NULL; - struct drm_mode_create_dumb create_dumb = { }; - struct drm_mode_map_dumb map_dumb = { }; - struct drm_mode_fb_cmd2 add_fb = { }; - unsigned int i; - int r; - - assert_return(out, -EINVAL); - assert_return(card, -EINVAL); - - fb = new0(grdrm_fb, 1); - if (!fb) - return -ENOMEM; - - /* TODO: we should choose a compatible format of the previous CRTC - * setting to allow page-flip to it. Only choose fallback if the - * previous setting was crap (non xrgb32'ish). */ - - fb->card = card; - fb->base.format = DRM_FORMAT_XRGB8888; - fb->base.width = mode->hdisplay; - fb->base.height = mode->vdisplay; - - for (i = 0; i < ELEMENTSOF(fb->base.maps); ++i) - fb->base.maps[i] = MAP_FAILED; - - create_dumb.width = fb->base.width; - create_dumb.height = fb->base.height; - create_dumb.bpp = 32; - - r = ioctl(card->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); - if (r < 0) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot create dumb buffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - fb->handles[0] = create_dumb.handle; - fb->base.strides[0] = create_dumb.pitch; - fb->sizes[0] = create_dumb.size; - - map_dumb.handle = fb->handles[0]; - - r = ioctl(card->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); - if (r < 0) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot map dumb buffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - fb->base.maps[0] = mmap(0, fb->sizes[0], PROT_WRITE, MAP_SHARED, card->fd, map_dumb.offset); - if (fb->base.maps[0] == MAP_FAILED) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot memory-map dumb buffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - memzero(fb->base.maps[0], fb->sizes[0]); - - add_fb.width = fb->base.width; - add_fb.height = fb->base.height; - add_fb.pixel_format = fb->base.format; - add_fb.flags = 0; - memcpy(add_fb.handles, fb->handles, sizeof(fb->handles)); - memcpy(add_fb.pitches, fb->base.strides, sizeof(fb->base.strides)); - memcpy(add_fb.offsets, fb->offsets, sizeof(fb->offsets)); - - r = ioctl(card->fd, DRM_IOCTL_MODE_ADDFB2, &add_fb); - if (r < 0) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot add framebuffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - fb->id = add_fb.fb_id; - - *out = fb; - fb = NULL; - return 0; -} - -grdrm_fb *grdrm_fb_free(grdrm_fb *fb) { - unsigned int i; - int r; - - if (!fb) - return NULL; - - assert(fb->card); - - if (fb->base.free_fn) - fb->base.free_fn(fb->base.data.ptr); - - if (fb->id > 0 && fb->card->fd >= 0) { - r = ioctl(fb->card->fd, DRM_IOCTL_MODE_RMFB, fb->id); - if (r < 0) - log_debug_errno(errno, "grdrm: %s: cannot delete framebuffer %" PRIu32 ": %m", - fb->card->base.name, fb->id); - } - - for (i = 0; i < ELEMENTSOF(fb->handles); ++i) { - struct drm_mode_destroy_dumb destroy_dumb = { }; - - if (fb->base.maps[i] != MAP_FAILED) - munmap(fb->base.maps[i], fb->sizes[i]); - - if (fb->handles[i] > 0 && fb->card->fd >= 0) { - destroy_dumb.handle = fb->handles[i]; - r = ioctl(fb->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); - if (r < 0) - log_debug_errno(errno, "grdrm: %s: cannot destroy dumb-buffer %" PRIu32 ": %m", - fb->card->base.name, fb->handles[i]); - } - } - - free(fb); - - return NULL; -} - -/* - * Pipes - */ - -static void grdrm_pipe_name(char *out, grdrm_crtc *crtc) { - /* @out must be at least of size GRDRM_PIPE_NAME_MAX */ - sprintf(out, "%s/%" PRIu32, crtc->object.card->base.name, crtc->object.id); -} - -static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_modeinfo *mode, size_t n_fbs) { - _cleanup_(grdev_pipe_freep) grdev_pipe *basepipe = NULL; - grdrm_card *card = crtc->object.card; - char name[GRDRM_PIPE_NAME_MAX]; - grdrm_pipe *pipe; - int r; - - assert_return(crtc, -EINVAL); - assert_return(grdev_is_drm_card(&card->base), -EINVAL); - - pipe = new0(grdrm_pipe, 1); - if (!pipe) - return -ENOMEM; - - basepipe = &pipe->base; - pipe->base = GRDEV_PIPE_INIT(&grdrm_pipe_vtable, &card->base); - pipe->crtc = crtc; - pipe->base.width = mode->hdisplay; - pipe->base.height = mode->vdisplay; - pipe->base.vrefresh = mode->vrefresh ? : 25; - - grdrm_pipe_name(name, crtc); - r = grdev_pipe_add(&pipe->base, name, n_fbs); - if (r < 0) - return r; - - if (out) - *out = pipe; - basepipe = NULL; - return 0; -} - -static void grdrm_pipe_free(grdev_pipe *basepipe) { - grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe); - size_t i; - - assert(pipe->crtc); - - for (i = 0; i < pipe->base.max_fbs; ++i) - if (pipe->base.fbs[i]) - grdrm_fb_free(fb_from_base(pipe->base.fbs[i])); - - free(pipe); -} - -static grdev_fb *grdrm_pipe_target(grdev_pipe *basepipe) { - grdrm_fb *fb; - size_t i; - - if (!basepipe->back) { - for (i = 0; i < basepipe->max_fbs; ++i) { - if (!basepipe->fbs[i]) - continue; - - fb = fb_from_base(basepipe->fbs[i]); - if (&fb->base == basepipe->front) - continue; - if (basepipe->flipping && fb->flipid) - continue; - - basepipe->back = &fb->base; - break; - } - } - - return basepipe->back; -} - -static void grdrm_pipe_enable(grdev_pipe *basepipe) { - grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe); - - pipe->crtc->applied = false; -} - -static void grdrm_pipe_disable(grdev_pipe *basepipe) { - grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe); - - pipe->crtc->applied = false; -} - -static const grdev_pipe_vtable grdrm_pipe_vtable = { - .free = grdrm_pipe_free, - .target = grdrm_pipe_target, - .enable = grdrm_pipe_enable, - .disable = grdrm_pipe_disable, -}; - -/* - * Cards - */ - -static void grdrm_name(char *out, dev_t devnum) { - /* @out must be at least of size GRDRM_CARD_NAME_MAX */ - sprintf(out, "drm/%u:%u", major(devnum), minor(devnum)); -} - -static void grdrm_card_print(grdrm_card *card) { - grdrm_object *object; - grdrm_crtc *crtc; - grdrm_encoder *encoder; - grdrm_connector *connector; - grdrm_plane *plane; - Iterator iter; - uint32_t i; - char *p, *buf; - - log_debug("grdrm: %s: state dump", card->base.name); - - log_debug(" crtcs:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - - if (crtc->kern.mode_set) - log_debug(" mode: %dx%d", crtc->kern.mode.hdisplay, crtc->kern.mode.vdisplay); - else - log_debug(" mode: <none>"); - } - - log_debug(" encoders:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_ENCODER) - continue; - - encoder = encoder_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - - if (encoder->kern.used_crtc) - log_debug(" crtc: %u", encoder->kern.used_crtc); - else - log_debug(" crtc: <none>"); - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * encoder->kern.n_crtcs + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < encoder->kern.n_crtcs; ++i) - p += sprintf(p, " %" PRIu32, encoder->kern.crtcs[i]); - - log_debug(" possible crtcs:%s", buf); - free(buf); - } - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * encoder->kern.n_clones + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < encoder->kern.n_clones; ++i) - p += sprintf(p, " %" PRIu32, encoder->kern.clones[i]); - - log_debug(" possible clones:%s", buf); - free(buf); - } - } - - log_debug(" connectors:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CONNECTOR) - continue; - - connector = connector_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - log_debug(" type: %" PRIu32 "-%" PRIu32 " connection: %" PRIu32 " subpixel: %" PRIu32 " extents: %" PRIu32 "x%" PRIu32, - connector->kern.type, connector->kern.type_id, connector->kern.connection, connector->kern.subpixel, - connector->kern.mm_width, connector->kern.mm_height); - - if (connector->kern.used_encoder) - log_debug(" encoder: %" PRIu32, connector->kern.used_encoder); - else - log_debug(" encoder: <none>"); - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * connector->kern.n_encoders + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < connector->kern.n_encoders; ++i) - p += sprintf(p, " %" PRIu32, connector->kern.encoders[i]); - - log_debug(" possible encoders:%s", buf); - free(buf); - } - - for (i = 0; i < connector->kern.n_modes; ++i) { - struct drm_mode_modeinfo *mode = &connector->kern.modes[i]; - log_debug(" mode: %" PRIu32 "x%" PRIu32, mode->hdisplay, mode->vdisplay); - } - } - - log_debug(" planes:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_PLANE) - continue; - - plane = plane_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - log_debug(" gamma-size: %" PRIu32, plane->kern.gamma_size); - - if (plane->kern.used_crtc) - log_debug(" crtc: %" PRIu32, plane->kern.used_crtc); - else - log_debug(" crtc: <none>"); - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * plane->kern.n_crtcs + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < plane->kern.n_crtcs; ++i) - p += sprintf(p, " %" PRIu32, plane->kern.crtcs[i]); - - log_debug(" possible crtcs:%s", buf); - free(buf); - } - - buf = malloc((DECIMAL_STR_MAX(unsigned int) + 3) * plane->kern.n_formats + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < plane->kern.n_formats; ++i) - p += sprintf(p, " 0x%x", (unsigned int)plane->kern.formats[i]); - - log_debug(" possible formats:%s", buf); - free(buf); - } - } -} - -static int grdrm_card_resync(grdrm_card *card) { - _cleanup_free_ uint32_t *crtc_ids = NULL, *encoder_ids = NULL, *connector_ids = NULL, *plane_ids = NULL; - uint32_t allocated = 0; - grdrm_object *object; - Iterator iter; - size_t tries; - int r; - - assert(card); - - card->async_hotplug = false; - allocated = 0; - - /* mark existing objects for possible removal */ - HASHMAP_FOREACH(object, card->object_map, iter) - object->present = false; - - for (tries = 0; tries < GRDRM_MAX_TRIES; ++tries) { - struct drm_mode_get_plane_res pres; - struct drm_mode_card_res res; - uint32_t i, max; - - if (allocated < card->max_ids) { - free(crtc_ids); - free(encoder_ids); - free(connector_ids); - free(plane_ids); - crtc_ids = new0(uint32_t, card->max_ids); - encoder_ids = new0(uint32_t, card->max_ids); - connector_ids = new0(uint32_t, card->max_ids); - plane_ids = new0(uint32_t, card->max_ids); - - if (!crtc_ids || !encoder_ids || !connector_ids || !plane_ids) - return -ENOMEM; - - allocated = card->max_ids; - } - - zero(res); - res.crtc_id_ptr = PTR_TO_UINT64(crtc_ids); - res.connector_id_ptr = PTR_TO_UINT64(connector_ids); - res.encoder_id_ptr = PTR_TO_UINT64(encoder_ids); - res.count_crtcs = allocated; - res.count_encoders = allocated; - res.count_connectors = allocated; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETRESOURCES, &res); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot retrieve drm resources: %m", - card->base.name); - return r; - } - - zero(pres); - pres.plane_id_ptr = PTR_TO_UINT64(plane_ids); - pres.count_planes = allocated; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETPLANERESOURCES, &pres); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot retrieve drm plane-resources: %m", - card->base.name); - return r; - } - - max = MAX(MAX(res.count_crtcs, res.count_encoders), - MAX(res.count_connectors, pres.count_planes)); - if (max > allocated) { - uint32_t n; - - n = ALIGN_POWER2(max); - if (!n || n > UINT16_MAX) { - log_debug("grdrm: %s: excessive DRM resource limit: %" PRIu32, - card->base.name, max); - return -ERANGE; - } - - /* retry with resized buffers */ - card->max_ids = n; - continue; - } - - /* mark available objects as present */ - - for (i = 0; i < res.count_crtcs; ++i) { - object = grdrm_find_object(card, crtc_ids[i]); - if (object && object->type == GRDRM_TYPE_CRTC) { - object->present = true; - object->index = i; - crtc_ids[i] = 0; - } - } - - for (i = 0; i < res.count_encoders; ++i) { - object = grdrm_find_object(card, encoder_ids[i]); - if (object && object->type == GRDRM_TYPE_ENCODER) { - object->present = true; - object->index = i; - encoder_ids[i] = 0; - } - } - - for (i = 0; i < res.count_connectors; ++i) { - object = grdrm_find_object(card, connector_ids[i]); - if (object && object->type == GRDRM_TYPE_CONNECTOR) { - object->present = true; - object->index = i; - connector_ids[i] = 0; - } - } - - for (i = 0; i < pres.count_planes; ++i) { - object = grdrm_find_object(card, plane_ids[i]); - if (object && object->type == GRDRM_TYPE_PLANE) { - object->present = true; - object->index = i; - plane_ids[i] = 0; - } - } - - /* drop removed objects */ - - HASHMAP_FOREACH(object, card->object_map, iter) - if (!object->present) - grdrm_object_free(object); - - /* add new objects */ - - card->n_crtcs = res.count_crtcs; - for (i = 0; i < res.count_crtcs; ++i) { - if (crtc_ids[i] < 1) - continue; - - r = grdrm_crtc_new(NULL, card, crtc_ids[i], i); - if (r < 0) - return r; - } - - card->n_encoders = res.count_encoders; - for (i = 0; i < res.count_encoders; ++i) { - if (encoder_ids[i] < 1) - continue; - - r = grdrm_encoder_new(NULL, card, encoder_ids[i], i); - if (r < 0) - return r; - } - - card->n_connectors = res.count_connectors; - for (i = 0; i < res.count_connectors; ++i) { - if (connector_ids[i] < 1) - continue; - - r = grdrm_connector_new(NULL, card, connector_ids[i], i); - if (r < 0) - return r; - } - - card->n_planes = pres.count_planes; - for (i = 0; i < pres.count_planes; ++i) { - if (plane_ids[i] < 1) - continue; - - r = grdrm_plane_new(NULL, card, plane_ids[i], i); - if (r < 0) - return r; - } - - /* re-sync objects after object_map is synced */ - - HASHMAP_FOREACH(object, card->object_map, iter) { - switch (object->type) { - case GRDRM_TYPE_CRTC: - r = grdrm_crtc_resync(crtc_from_object(object)); - break; - case GRDRM_TYPE_ENCODER: - r = grdrm_encoder_resync(encoder_from_object(object)); - break; - case GRDRM_TYPE_CONNECTOR: - r = grdrm_connector_resync(connector_from_object(object)); - break; - case GRDRM_TYPE_PLANE: - r = grdrm_plane_resync(plane_from_object(object)); - break; - default: - assert_not_reached("grdrm: invalid object type"); - r = 0; - } - - if (r < 0) - return r; - - if (card->async_hotplug) - break; - } - - /* if modeset objects change during sync, start over */ - if (card->async_hotplug) { - card->async_hotplug = false; - continue; - } - - /* cache crtc/connector relationship */ - HASHMAP_FOREACH(object, card->object_map, iter) { - grdrm_connector *connector; - grdrm_encoder *encoder; - grdrm_crtc *crtc; - - if (object->type != GRDRM_TYPE_CONNECTOR) - continue; - - connector = connector_from_object(object); - if (connector->kern.connection != 1 || connector->kern.used_encoder < 1) - continue; - - object = grdrm_find_object(card, connector->kern.used_encoder); - if (!object || object->type != GRDRM_TYPE_ENCODER) - continue; - - encoder = encoder_from_object(object); - if (encoder->kern.used_crtc < 1) - continue; - - object = grdrm_find_object(card, encoder->kern.used_crtc); - if (!object || object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - assert(crtc->kern.n_used_connectors < crtc->kern.max_used_connectors); - crtc->kern.used_connectors[crtc->kern.n_used_connectors++] = connector->object.id; - } - - /* cache old crtc settings for later restore */ - HASHMAP_FOREACH(object, card->object_map, iter) { - grdrm_crtc *crtc; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - - /* Save data if it is the first time we refresh the CRTC. This data can - * be used optionally to restore any previous configuration. For - * instance, it allows us to restore VT configurations after we close - * our session again. */ - if (!crtc->old.set) { - crtc->old.fb = crtc->kern.used_fb; - crtc->old.fb_x = crtc->kern.fb_offset_x; - crtc->old.fb_y = crtc->kern.fb_offset_y; - crtc->old.gamma = crtc->kern.gamma_size; - crtc->old.n_connectors = crtc->kern.n_used_connectors; - if (crtc->old.n_connectors) - memcpy(crtc->old.connectors, crtc->kern.used_connectors, sizeof(uint32_t) * crtc->old.n_connectors); - crtc->old.mode_set = crtc->kern.mode_set; - crtc->old.mode = crtc->kern.mode; - crtc->old.set = true; - } - } - - /* everything synced */ - break; - } - - if (tries >= GRDRM_MAX_TRIES) { - /* - * Ugh! We were unable to sync the DRM card state due to heavy - * hotplugging. This should never happen, so print a debug - * message and bail out. The next uevent will trigger - * this again. - */ - - log_debug("grdrm: %s: hotplug-storm when syncing card", card->base.name); - return -EFAULT; - } - - return 0; -} - -static bool card_configure_crtc(grdrm_crtc *crtc, grdrm_connector *connector) { - grdrm_card *card = crtc->object.card; - grdrm_encoder *encoder; - grdrm_object *object; - uint32_t i, j; - - if (crtc->object.assigned || connector->object.assigned) - return false; - if (connector->kern.connection != 1) - return false; - - for (i = 0; i < connector->kern.n_encoders; ++i) { - object = grdrm_find_object(card, connector->kern.encoders[i]); - if (!object || object->type != GRDRM_TYPE_ENCODER) - continue; - - encoder = encoder_from_object(object); - for (j = 0; j < encoder->kern.n_crtcs; ++j) { - if (encoder->kern.crtcs[j] == crtc->object.id) { - grdrm_crtc_assign(crtc, connector); - return true; - } - } - } - - return false; -} - -static void grdrm_card_configure(grdrm_card *card) { - /* - * Modeset Configuration - * This is where we update our modeset configuration and assign - * connectors to CRTCs. This means, each connector that we want to - * enable needs a CRTC, disabled (or unavailable) connectors are left - * alone in the dark. Once all CRTCs are assigned, the remaining CRTCs - * are disabled. - * Sounds trivial, but there're several caveats: - * - * * Multiple connectors can be driven by the same CRTC. This is - * known as 'hardware clone mode'. Advantage over software clone - * mode is that only a single CRTC is needed to drive multiple - * displays. However, few hardware supports this and it's a huge - * headache to configure on dynamic demands. Therefore, we only - * support it if configured statically beforehand. - * - * * CRTCs are not created equal. Some might be much more powerful - * than others, including more advanced plane support. So far, our - * CRTC selection is random. You need to supply static - * configuration if you want special setups. So far, there is no - * proper way to do advanced CRTC selection on dynamic demands. It - * is not really clear which demands require what CRTC, so, like - * everyone else, we do random CRTC selection unless explicitly - * states otherwise. - * - * * Each Connector has a list of possible encoders that can drive - * it, and each encoder has a list of possible CRTCs. If this graph - * is a tree, assignment is trivial. However, if not, we cannot - * reliably decide on configurations beforehand. The encoder is - * always selected by the kernel, so we have to actually set a mode - * to know which encoder is used. There is no way to ask the kernel - * whether a given configuration is possible. This will change with - * atomic-modesetting, but until then, we keep our configurations - * simple and assume they work all just fine. If one fails - * unexpectedly, we print a warning and disable it. - * - * Configuring a card consists of several steps: - * - * 1) First of all, we apply any user-configuration. If a user wants - * a fixed configuration, we apply it and preserve it. - * So far, we don't support user configuration files, so this step - * is skipped. - * - * 2) Secondly, we need to apply any quirks from hwdb. Some hardware - * might only support limited configurations or require special - * CRTC/Connector mappings. We read this from hwdb and apply it, if - * present. - * So far, we don't support this as there is no known quirk, so - * this step is skipped. - * - * 3) As deep modesets are expensive, we try to avoid them if - * possible. Therefore, we read the current configuration from the - * kernel and try to preserve it, if compatible with our demands. - * If not, we break it and reassign it in a following step. - * - * 4) The main step involves configuring all remaining objects. By - * default, all available connectors are enabled, except for those - * disabled by user-configuration. We lookup a suitable CRTC for - * each connector and assign them. As there might be more - * connectors than CRTCs, we apply some ordering so users can - * select which connectors are more important right now. - * So far, we only apply the default ordering, more might be added - * in the future. - */ - - grdrm_object *object; - grdrm_crtc *crtc; - Iterator i, j; - - /* clear assignments */ - HASHMAP_FOREACH(object, card->object_map, i) - object->assigned = false; - - /* preserve existing configurations */ - HASHMAP_FOREACH(object, card->object_map, i) { - if (object->type != GRDRM_TYPE_CRTC || object->assigned) - continue; - - crtc = crtc_from_object(object); - - if (crtc->applied) { - /* If our mode is set, preserve it. If no connector is - * set, modeset either failed or the pipe is unused. In - * both cases, leave it alone. It might be tried again - * below in case there're remaining connectors. - * Otherwise, try restoring the assignments. If they - * are no longer valid, leave the pipe untouched. */ - - if (crtc->set.n_connectors < 1) - continue; - - assert(crtc->set.n_connectors == 1); - - object = grdrm_find_object(card, crtc->set.connectors[0]); - if (!object || object->type != GRDRM_TYPE_CONNECTOR) - continue; - - card_configure_crtc(crtc, connector_from_object(object)); - } else if (crtc->kern.mode_set && crtc->kern.n_used_connectors != 1) { - /* If our mode is not set on the pipe, we know the kern - * information is valid. Try keeping it. If it's not - * possible, leave the pipe untouched for later - * assignements. */ - - object = grdrm_find_object(card, crtc->kern.used_connectors[0]); - if (!object || object->type != GRDRM_TYPE_CONNECTOR) - continue; - - card_configure_crtc(crtc, connector_from_object(object)); - } - } - - /* assign remaining objects */ - HASHMAP_FOREACH(object, card->object_map, i) { - if (object->type != GRDRM_TYPE_CRTC || object->assigned) - continue; - - crtc = crtc_from_object(object); - - HASHMAP_FOREACH(object, card->object_map, j) { - if (object->type != GRDRM_TYPE_CONNECTOR) - continue; - - if (card_configure_crtc(crtc, connector_from_object(object))) - break; - } - - if (!crtc->object.assigned) - grdrm_crtc_assign(crtc, NULL); - } - - /* expose configuration */ - HASHMAP_FOREACH(object, card->object_map, i) { - if (object->type != GRDRM_TYPE_CRTC) - continue; - - grdrm_crtc_expose(crtc_from_object(object)); - } -} - -static void grdrm_card_hotplug(grdrm_card *card) { - int r; - - assert(card); - - if (!card->running) - return; - - log_debug("grdrm: %s/%s: reconfigure card", card->base.session->name, card->base.name); - - card->ready = false; - r = grdrm_card_resync(card); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot re-sync card: %m", - card->base.session->name, card->base.name); - return; - } - - grdev_session_pin(card->base.session); - - /* debug statement to print card information */ - if (0) - grdrm_card_print(card); - - grdrm_card_configure(card); - card->ready = true; - card->hotplug = false; - - grdev_session_unpin(card->base.session); -} - -static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - grdrm_card *card = userdata; - struct drm_event_vblank *vblank; - struct drm_event *event; - uint32_t id, counter; - grdrm_object *object; - char buf[4096]; - size_t len; - ssize_t l; - - if (revents & (EPOLLHUP | EPOLLERR)) { - /* Immediately close device on HUP; no need to flush pending - * data.. there're no events we care about here. */ - log_debug("grdrm: %s/%s: HUP", card->base.session->name, card->base.name); - grdrm_card_close(card); - return 0; - } - - if (revents & (EPOLLIN)) { - l = read(card->fd, buf, sizeof(buf)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - log_debug_errno(errno, "grdrm: %s/%s: read error: %m", - card->base.session->name, card->base.name); - grdrm_card_close(card); - return 0; - } - - for (len = l; len > 0; len -= event->length) { - event = (void*)buf; - - if (len < sizeof(*event) || len < event->length) { - log_debug("grdrm: %s/%s: truncated event", - card->base.session->name, card->base.name); - break; - } - - switch (event->type) { - case DRM_EVENT_FLIP_COMPLETE: - vblank = (void*)event; - if (event->length < sizeof(*vblank)) { - log_debug("grdrm: %s/%s: truncated vblank event", - card->base.session->name, card->base.name); - break; - } - - grdrm_decode_vblank_data(vblank->user_data, &id, &counter); - object = grdrm_find_object(card, id); - if (!object || object->type != GRDRM_TYPE_CRTC) - break; - - grdrm_crtc_flip_complete(crtc_from_object(object), counter, vblank); - break; - } - } - } - - return 0; -} - -static int grdrm_card_add(grdrm_card *card, const char *name) { - assert(card); - assert(card->fd < 0); - - card->object_map = hashmap_new(&trivial_hash_ops); - if (!card->object_map) - return -ENOMEM; - - return grdev_card_add(&card->base, name); -} - -static void grdrm_card_destroy(grdrm_card *card) { - assert(card); - assert(!card->running); - assert(card->fd < 0); - assert(hashmap_size(card->object_map) == 0); - - hashmap_free(card->object_map); -} - -static void grdrm_card_commit(grdev_card *basecard) { - grdrm_card *card = grdrm_card_from_base(basecard); - grdrm_object *object; - Iterator iter; - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (!card->ready) - break; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - grdrm_crtc_commit(crtc_from_object(object)); - } -} - -static void grdrm_card_restore(grdev_card *basecard) { - grdrm_card *card = grdrm_card_from_base(basecard); - grdrm_object *object; - Iterator iter; - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (!card->ready) - break; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - grdrm_crtc_restore(crtc_from_object(object)); - } -} - -static void grdrm_card_enable(grdrm_card *card) { - assert(card); - - if (card->fd < 0 || card->running) - return; - - /* ignore cards without DUMB_BUFFER capability */ - if (!card->cap_dumb) - return; - - assert(card->fd_src); - - log_debug("grdrm: %s/%s: enable", card->base.session->name, card->base.name); - - card->running = true; - sd_event_source_set_enabled(card->fd_src, SD_EVENT_ON); - grdrm_card_hotplug(card); -} - -static void grdrm_card_disable(grdrm_card *card) { - grdrm_object *object; - Iterator iter; - - assert(card); - - if (card->fd < 0 || !card->running) - return; - - assert(card->fd_src); - - log_debug("grdrm: %s/%s: disable", card->base.session->name, card->base.name); - - card->running = false; - card->ready = false; - sd_event_source_set_enabled(card->fd_src, SD_EVENT_OFF); - - /* stop all pipes */ - HASHMAP_FOREACH(object, card->object_map, iter) { - grdrm_crtc *crtc; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - crtc->applied = false; - if (crtc->pipe) - grdev_pipe_ready(&crtc->pipe->base, false); - } -} - -static int grdrm_card_open(grdrm_card *card, int dev_fd) { - _cleanup_(grdev_session_unpinp) grdev_session *pin = NULL; - _cleanup_close_ int fd = dev_fd; - struct drm_get_cap cap; - int r, flags; - - assert(card); - assert(dev_fd >= 0); - assert(card->fd != dev_fd); - - pin = grdev_session_pin(card->base.session); - grdrm_card_close(card); - - log_debug("grdrm: %s/%s: open", card->base.session->name, card->base.name); - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = fd_cloexec(fd, true); - if (r < 0) - return r; - - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -errno; - if ((flags & O_ACCMODE) != O_RDWR) - return -EACCES; - - r = sd_event_add_io(card->base.session->context->event, - &card->fd_src, - fd, - EPOLLHUP | EPOLLERR | EPOLLIN, - grdrm_card_io_fn, - card); - if (r < 0) - return r; - - sd_event_source_set_enabled(card->fd_src, SD_EVENT_OFF); - - card->hotplug = true; - card->fd = fd; - fd = -1; - - /* cache DUMB_BUFFER capability */ - cap.capability = DRM_CAP_DUMB_BUFFER; - cap.value = 0; - r = ioctl(card->fd, DRM_IOCTL_GET_CAP, &cap); - card->cap_dumb = r >= 0 && cap.value; - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot retrieve DUMB_BUFFER capability: %m", - card->base.session->name, card->base.name); - else if (!card->cap_dumb) - log_debug("grdrm: %s/%s: DUMB_BUFFER capability not supported", - card->base.session->name, card->base.name); - - /* cache TIMESTAMP_MONOTONIC capability */ - cap.capability = DRM_CAP_TIMESTAMP_MONOTONIC; - cap.value = 0; - r = ioctl(card->fd, DRM_IOCTL_GET_CAP, &cap); - card->cap_monotonic = r >= 0 && cap.value; - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot retrieve TIMESTAMP_MONOTONIC capability: %m", - card->base.session->name, card->base.name); - else if (!card->cap_monotonic) - log_debug("grdrm: %s/%s: TIMESTAMP_MONOTONIC is disabled globally, fix this NOW!", - card->base.session->name, card->base.name); - - return 0; -} - -static void grdrm_card_close(grdrm_card *card) { - grdrm_object *object; - - if (card->fd < 0) - return; - - log_debug("grdrm: %s/%s: close", card->base.session->name, card->base.name); - - grdrm_card_disable(card); - - card->fd_src = sd_event_source_unref(card->fd_src); - card->fd = safe_close(card->fd); - - grdev_session_pin(card->base.session); - while ((object = hashmap_first(card->object_map))) - grdrm_object_free(object); - grdev_session_unpin(card->base.session); -} - -static bool grdrm_card_async(grdrm_card *card, int r) { - switch (r) { - case -EACCES: - /* If we get EACCES on runtime DRM calls, we lost DRM-Master - * (or we did something terribly wrong). Immediately disable - * the card, so we stop all pipes and wait to be activated - * again. */ - grdrm_card_disable(card); - break; - case -ENOENT: - /* DRM objects can be hotplugged at any time. If an object is - * removed that we use, we remember that state so a following - * call can test for this. - * Note that we also get a uevent as followup, this will resync - * the whole device. */ - card->async_hotplug = true; - break; - } - - return !card->ready; -} - -/* - * Unmanaged Cards - * The unmanaged DRM card opens the device node for a given DRM device - * directly (/dev/dri/cardX) and thus needs sufficient privileges. It opens - * the device only if we really require it and releases it as soon as we're - * disabled or closed. - * The unmanaged element can be used in all situations where you have direct - * access to DRM device nodes. Unlike managed DRM elements, it can be used - * outside of user sessions and in emergency situations where logind is not - * available. - */ - -static void unmanaged_card_enable(grdev_card *basecard) { - unmanaged_card *cu = unmanaged_card_from_base(basecard); - int r, fd; - - if (cu->card.fd < 0) { - /* try open on activation if it failed during allocation */ - fd = open(cu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - /* not fatal; simply ignore the device */ - log_debug_errno(errno, "grdrm: %s/%s: cannot open node %s: %m", - basecard->session->name, basecard->name, cu->devnode); - return; - } - - /* we might already be DRM-Master by open(); that's fine */ - - r = grdrm_card_open(&cu->card, fd); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - basecard->session->name, basecard->name); - return; - } - } - - r = ioctl(cu->card.fd, DRM_IOCTL_SET_MASTER, 0); - if (r < 0) { - log_debug_errno(errno, "grdrm: %s/%s: cannot acquire DRM-Master: %m", - basecard->session->name, basecard->name); - return; - } - - grdrm_card_enable(&cu->card); -} - -static void unmanaged_card_disable(grdev_card *basecard) { - unmanaged_card *cu = unmanaged_card_from_base(basecard); - - grdrm_card_disable(&cu->card); -} - -static int unmanaged_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) { - _cleanup_(grdev_card_freep) grdev_card *basecard = NULL; - char name[GRDRM_CARD_NAME_MAX]; - unmanaged_card *cu; - const char *devnode; - dev_t devnum; - int r, fd; - - assert_return(session, -EINVAL); - assert_return(ud, -EINVAL); - - devnode = udev_device_get_devnode(ud); - devnum = udev_device_get_devnum(ud); - if (!devnode || devnum == 0) - return -ENODEV; - - grdrm_name(name, devnum); - - cu = new0(unmanaged_card, 1); - if (!cu) - return -ENOMEM; - - basecard = &cu->card.base; - cu->card = GRDRM_CARD_INIT(&unmanaged_card_vtable, session); - - cu->devnode = strdup(devnode); - if (!cu->devnode) - return -ENOMEM; - - r = grdrm_card_add(&cu->card, name); - if (r < 0) - return r; - - /* try to open but ignore errors */ - fd = open(cu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - /* not fatal; allow uaccess based control on activation */ - log_debug_errno(errno, "grdrm: %s/%s: cannot open node %s: %m", - basecard->session->name, basecard->name, cu->devnode); - } else { - /* We might get DRM-Master implicitly on open(); drop it immediately - * so we acquire it only once we're actually enabled. We don't - * really care whether this call fails or not, but let's log any - * weird errors, anyway. */ - r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0); - if (r < 0 && errno != EACCES && errno != EINVAL) - log_debug_errno(errno, "grdrm: %s/%s: cannot drop DRM-Master: %m", - basecard->session->name, basecard->name); - - r = grdrm_card_open(&cu->card, fd); - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - basecard->session->name, basecard->name); - } - - if (out) - *out = basecard; - basecard = NULL; - return 0; -} - -static void unmanaged_card_free(grdev_card *basecard) { - unmanaged_card *cu = unmanaged_card_from_base(basecard); - - assert(!basecard->enabled); - - grdrm_card_close(&cu->card); - grdrm_card_destroy(&cu->card); - free(cu->devnode); - free(cu); -} - -static const grdev_card_vtable unmanaged_card_vtable = { - .free = unmanaged_card_free, - .enable = unmanaged_card_enable, - .disable = unmanaged_card_disable, - .commit = grdrm_card_commit, - .restore = grdrm_card_restore, -}; - -/* - * Managed Cards - * The managed DRM card uses systemd-logind to acquire DRM devices. This - * means, we do not open the device node /dev/dri/cardX directly. Instead, - * logind passes us a file-descriptor whenever our session is activated. Thus, - * we don't need access to the device node directly. - * Furthermore, whenever the session is put asleep, logind revokes the - * file-descriptor so we loose access to the device. - * Managed DRM cards should be preferred over unmanaged DRM cards whenever - * you run inside a user session with exclusive device access. - */ - -static void managed_card_enable(grdev_card *card) { - managed_card *cm = managed_card_from_base(card); - - /* If the device is manually re-enabled, we try to resume our card - * management. Note that we have no control over DRM-Master and the fd, - * so we have to take over the state from the last logind event. */ - - if (cm->master) - grdrm_card_enable(&cm->card); -} - -static void managed_card_disable(grdev_card *card) { - managed_card *cm = managed_card_from_base(card); - - /* If the device is manually disabled, we keep the FD but put our card - * management asleep. This way, we can wake up at any time, but don't - * touch the device while asleep. */ - - grdrm_card_disable(&cm->card); -} - -static int managed_card_pause_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - managed_card *cm = userdata; - grdev_session *session = cm->card.base.session; - uint32_t major, minor; - const char *mode; - int r; - - /* - * We get PauseDevice() signals from logind whenever a device we - * requested was, or is about to be, paused. Arguments are major/minor - * number of the device and the mode of the operation. - * In case the event is not about our device, we ignore it. Otherwise, - * we treat it as asynchronous DRM-DROP-MASTER. Note that we might have - * already handled an EACCES error from a modeset ioctl, in which case - * we already disabled the device. - * - * @mode can be one of the following: - * "pause": The device is about to be paused. We must react - * immediately and respond with PauseDeviceComplete(). Once - * we replied, logind will pause the device. Note that - * logind might apply any kind of timeout and force pause - * the device if we don't respond in a timely manner. In - * this case, we will receive a second PauseDevice event - * with @mode set to "force" (or similar). - * "force": The device was disabled forecfully by logind. DRM-Master - * was already dropped. This is just an asynchronous - * notification so we can put the device asleep (in case - * we didn't already notice the dropped DRM-Master). - * "gone": This is like "force" but is sent if the device was - * paused due to a device-removal event. - * - * We always handle PauseDevice signals as "force" as we properly - * support asynchronously dropping DRM-Master, anyway. But in case - * logind sent mode "pause", we also call PauseDeviceComplete() to - * immediately acknowledge the request. - */ - - r = sd_bus_message_read(signal, "uus", &major, &minor, &mode); - if (r < 0) { - log_debug("grdrm: %s/%s: erroneous PauseDevice signal", - session->name, cm->card.base.name); - return 0; - } - - /* not our device? */ - if (makedev(major, minor) != cm->devnum) - return 0; - - cm->master = false; - grdrm_card_disable(&cm->card); - - if (streq(mode, "pause")) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - - /* - * Sending PauseDeviceComplete() is racy if logind triggers the - * timeout. That is, if we take too long and logind pauses the - * device by sending a forced PauseDevice, our - * PauseDeviceComplete call will be stray. That's fine, though. - * logind ignores such stray calls. Only if logind also sent a - * further PauseDevice() signal, it might match our call - * incorrectly to the newer PauseDevice(). That's fine, too, as - * we handle that event asynchronously, anyway. Therefore, - * whatever happens, we're fine. Yay! - */ - - r = sd_bus_message_new_method_call(session->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "PauseDeviceComplete"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major, minor); - if (r >= 0) - r = sd_bus_send(session->context->sysbus, m, NULL); - } - - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot send PauseDeviceComplete: %m", - session->name, cm->card.base.name); - } - - return 0; -} - -static int managed_card_resume_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - managed_card *cm = userdata; - grdev_session *session = cm->card.base.session; - uint32_t major, minor; - int r, fd; - - /* - * We get ResumeDevice signals whenever logind resumed a previously - * paused device. The arguments contain the major/minor number of the - * related device and a new file-descriptor for the freshly opened - * device-node. - * If the signal is not about our device, we simply ignore it. - * Otherwise, we immediately resume the device. Note that we drop the - * new file-descriptor as we already have one from TakeDevice(). logind - * preserves the file-context across pause/resume for DRM but only - * drops/acquires DRM-Master accordingly. This way, our context (like - * DRM-FBs and BOs) is preserved. - */ - - r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd); - if (r < 0) { - log_debug("grdrm: %s/%s: erroneous ResumeDevice signal", - session->name, cm->card.base.name); - return 0; - } - - /* not our device? */ - if (makedev(major, minor) != cm->devnum) - return 0; - - if (cm->card.fd < 0) { - /* This shouldn't happen. We should already own an FD from - * TakeDevice(). However, let's be safe and use this FD in case - * we really don't have one. There is no harm in doing this - * and our code works fine this way. */ - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "grdrm: %s/%s: cannot duplicate fd: %m", - session->name, cm->card.base.name); - return 0; - } - - r = grdrm_card_open(&cm->card, fd); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - session->name, cm->card.base.name); - return 0; - } - } - - cm->master = true; - if (cm->card.base.enabled) - grdrm_card_enable(&cm->card); - - return 0; -} - -static int managed_card_setup_bus(managed_card *cm) { - grdev_session *session = cm->card.base.session; - _cleanup_free_ char *match = NULL; - int r; - - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='PauseDevice'," - "path='", session->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(session->context->sysbus, - &cm->slot_pause_device, - match, - managed_card_pause_device_fn, - cm); - if (r < 0) - return r; - - free(match); - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='ResumeDevice'," - "path='", session->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(session->context->sysbus, - &cm->slot_resume_device, - match, - managed_card_resume_device_fn, - cm); - if (r < 0) - return r; - - return 0; -} - -static int managed_card_take_device_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - managed_card *cm = userdata; - grdev_session *session = cm->card.base.session; - int r, paused, fd; - - cm->slot_take_device = sd_bus_slot_unref(cm->slot_take_device); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("grdrm: %s/%s: TakeDevice failed: %s: %s", - session->name, cm->card.base.name, error->name, error->message); - return 0; - } - - cm->acquired = true; - - r = sd_bus_message_read(reply, "hb", &fd, &paused); - if (r < 0) { - log_debug("grdrm: %s/%s: erroneous TakeDevice reply", - session->name, cm->card.base.name); - return 0; - } - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "grdrm: %s/%s: cannot duplicate fd: %m", - session->name, cm->card.base.name); - return 0; - } - - r = grdrm_card_open(&cm->card, fd); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - session->name, cm->card.base.name); - return 0; - } - - if (!paused && cm->card.base.enabled) - grdrm_card_enable(&cm->card); - - return 0; -} - -static void managed_card_take_device(managed_card *cm) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - grdev_session *session = cm->card.base.session; - int r; - - r = sd_bus_message_new_method_call(session->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "TakeDevice"); - if (r < 0) - goto error; - - r = sd_bus_message_append(m, "uu", major(cm->devnum), minor(cm->devnum)); - if (r < 0) - goto error; - - r = sd_bus_call_async(session->context->sysbus, - &cm->slot_take_device, - m, - managed_card_take_device_fn, - cm, - 0); - if (r < 0) - goto error; - - cm->requested = true; - return; - -error: - log_debug_errno(r, "grdrm: %s/%s: cannot send TakeDevice request: %m", - session->name, cm->card.base.name); -} - -static void managed_card_release_device(managed_card *cm) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - grdev_session *session = cm->card.base.session; - int r; - - /* - * If TakeDevice() is pending or was successful, make sure to - * release the device again. We don't care for return-values, - * so send it without waiting or callbacks. - * If a failed TakeDevice() is pending, but someone else took - * the device on the same bus-connection, we might incorrectly - * release their device. This is an unlikely race, though. - * Furthermore, you really shouldn't have two users of the - * controller-API on the same session, on the same devices, *AND* on - * the same bus-connection. So we don't care for that race.. - */ - - grdrm_card_close(&cm->card); - cm->requested = false; - - if (!cm->acquired && !cm->slot_take_device) - return; - - cm->slot_take_device = sd_bus_slot_unref(cm->slot_take_device); - cm->acquired = false; - - r = sd_bus_message_new_method_call(session->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "ReleaseDevice"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major(cm->devnum), minor(cm->devnum)); - if (r >= 0) - r = sd_bus_send(session->context->sysbus, m, NULL); - } - - if (r < 0 && r != -ENOTCONN) - log_debug_errno(r, "grdrm: %s/%s: cannot send ReleaseDevice: %m", - session->name, cm->card.base.name); -} - -static int managed_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) { - _cleanup_(grdev_card_freep) grdev_card *basecard = NULL; - char name[GRDRM_CARD_NAME_MAX]; - managed_card *cm; - dev_t devnum; - int r; - - assert_return(session, -EINVAL); - assert_return(session->managed, -EINVAL); - assert_return(session->context->sysbus, -EINVAL); - assert_return(ud, -EINVAL); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return -ENODEV; - - grdrm_name(name, devnum); - - cm = new0(managed_card, 1); - if (!cm) - return -ENOMEM; - - basecard = &cm->card.base; - cm->card = GRDRM_CARD_INIT(&managed_card_vtable, session); - cm->devnum = devnum; - - r = managed_card_setup_bus(cm); - if (r < 0) - return r; - - r = grdrm_card_add(&cm->card, name); - if (r < 0) - return r; - - managed_card_take_device(cm); - - if (out) - *out = basecard; - basecard = NULL; - return 0; -} - -static void managed_card_free(grdev_card *basecard) { - managed_card *cm = managed_card_from_base(basecard); - - assert(!basecard->enabled); - - managed_card_release_device(cm); - cm->slot_resume_device = sd_bus_slot_unref(cm->slot_resume_device); - cm->slot_pause_device = sd_bus_slot_unref(cm->slot_pause_device); - grdrm_card_destroy(&cm->card); - free(cm); -} - -static const grdev_card_vtable managed_card_vtable = { - .free = managed_card_free, - .enable = managed_card_enable, - .disable = managed_card_disable, - .commit = grdrm_card_commit, - .restore = grdrm_card_restore, -}; - -/* - * Generic Constructor - * Instead of relying on the caller to choose between managed and unmanaged - * DRM devices, the grdev_drm_new() constructor does that for you (by - * looking at session->managed). - */ - -bool grdev_is_drm_card(grdev_card *basecard) { - return basecard && (basecard->vtable == &unmanaged_card_vtable || - basecard->vtable == &managed_card_vtable); -} - -grdev_card *grdev_find_drm_card(grdev_session *session, dev_t devnum) { - char name[GRDRM_CARD_NAME_MAX]; - - assert_return(session, NULL); - assert_return(devnum != 0, NULL); - - grdrm_name(name, devnum); - return grdev_find_card(session, name); -} - -int grdev_drm_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) { - assert_return(session, -EINVAL); - assert_return(ud, -EINVAL); - - return session->managed ? managed_card_new(out, session, ud) : unmanaged_card_new(out, session, ud); -} - -void grdev_drm_card_hotplug(grdev_card *basecard, struct udev_device *ud) { - const char *p, *action; - grdrm_card *card; - dev_t devnum; - - assert(basecard); - assert(grdev_is_drm_card(basecard)); - assert(ud); - - card = grdrm_card_from_base(basecard); - - action = udev_device_get_action(ud); - if (!action || streq(action, "add") || streq(action, "remove")) { - /* If we get add/remove events on DRM nodes without devnum, we - * got hotplugged DRM objects so refresh the device. */ - devnum = udev_device_get_devnum(ud); - if (devnum == 0) { - card->hotplug = true; - grdrm_card_hotplug(card); - } - } else if (streq_ptr(action, "change")) { - /* A change event with HOTPLUG=1 is sent whenever a connector - * changed state. Refresh the device to update our state. */ - p = udev_device_get_property_value(ud, "HOTPLUG"); - if (streq_ptr(p, "1")) { - card->hotplug = true; - grdrm_card_hotplug(card); - } - } -} diff --git a/src/libsystemd-terminal/grdev-internal.h b/src/libsystemd-terminal/grdev-internal.h deleted file mode 100644 index 46d65f0248..0000000000 --- a/src/libsystemd-terminal/grdev-internal.h +++ /dev/null @@ -1,251 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <inttypes.h> -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "list.h" -#include "util.h" -#include "grdev.h" - -typedef struct grdev_tile grdev_tile; -typedef struct grdev_display_cache grdev_display_cache; - -typedef struct grdev_pipe_vtable grdev_pipe_vtable; -typedef struct grdev_pipe grdev_pipe; -typedef struct grdev_card_vtable grdev_card_vtable; -typedef struct grdev_card grdev_card; - -/* - * DRM cards - */ - -bool grdev_is_drm_card(grdev_card *card); -grdev_card *grdev_find_drm_card(grdev_session *session, dev_t devnum); -int grdev_drm_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud); -void grdev_drm_card_hotplug(grdev_card *card, struct udev_device *ud); - -/* - * Displays - */ - -enum { - GRDEV_TILE_LEAF, - GRDEV_TILE_NODE, - GRDEV_TILE_CNT -}; - -struct grdev_tile { - LIST_FIELDS(grdev_tile, children_by_node); - grdev_tile *parent; - grdev_display *display; - - uint32_t x; - uint32_t y; - unsigned int rotate; - unsigned int flip; - uint32_t cache_w; - uint32_t cache_h; - - unsigned int type; - - union { - struct { - grdev_pipe *pipe; - } leaf; - - struct { - size_t n_children; - LIST_HEAD(grdev_tile, child_list); - } node; - }; -}; - -int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe); -int grdev_tile_new_node(grdev_tile **out); -grdev_tile *grdev_tile_free(grdev_tile *tile); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_tile*, grdev_tile_free); - -struct grdev_display { - grdev_session *session; - char *name; - void *userdata; - - size_t n_leafs; - grdev_tile *tile; - - size_t n_pipes; - size_t max_pipes; - - uint32_t width; - uint32_t height; - - struct grdev_display_cache { - grdev_pipe *pipe; - grdev_display_target target; - - bool incomplete : 1; - } *pipes; - - bool enabled : 1; - bool public : 1; - bool modified : 1; - bool framed : 1; -}; - -grdev_display *grdev_find_display(grdev_session *session, const char *name); - -int grdev_display_new(grdev_display **out, grdev_session *session, const char *name); -grdev_display *grdev_display_free(grdev_display *display); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_display*, grdev_display_free); - -/* - * Pipes - */ - -struct grdev_pipe_vtable { - void (*free) (grdev_pipe *pipe); - void (*enable) (grdev_pipe *pipe); - void (*disable) (grdev_pipe *pipe); - grdev_fb *(*target) (grdev_pipe *pipe); -}; - -struct grdev_pipe { - const grdev_pipe_vtable *vtable; - grdev_card *card; - char *name; - - grdev_tile *tile; - grdev_display_cache *cache; - sd_event_source *vsync_src; - - uint32_t width; - uint32_t height; - uint32_t vrefresh; - - size_t max_fbs; - grdev_fb *front; - grdev_fb *back; - grdev_fb **fbs; - - bool enabled : 1; - bool running : 1; - bool flip : 1; - bool flipping : 1; -}; - -#define GRDEV_PIPE_INIT(_vtable, _card) ((grdev_pipe){ \ - .vtable = (_vtable), \ - .card = (_card), \ - }) - -grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name); - -int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs); -grdev_pipe *grdev_pipe_free(grdev_pipe *pipe); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_pipe*, grdev_pipe_free); - -void grdev_pipe_ready(grdev_pipe *pipe, bool running); -void grdev_pipe_frame(grdev_pipe *pipe); -void grdev_pipe_schedule(grdev_pipe *pipe, uint64_t frames); - -/* - * Cards - */ - -struct grdev_card_vtable { - void (*free) (grdev_card *card); - void (*enable) (grdev_card *card); - void (*disable) (grdev_card *card); - void (*commit) (grdev_card *card); - void (*restore) (grdev_card *card); -}; - -struct grdev_card { - const grdev_card_vtable *vtable; - grdev_session *session; - char *name; - - Hashmap *pipe_map; - - bool enabled : 1; - bool modified : 1; -}; - -#define GRDEV_CARD_INIT(_vtable, _session) ((grdev_card){ \ - .vtable = (_vtable), \ - .session = (_session), \ - }) - -grdev_card *grdev_find_card(grdev_session *session, const char *name); - -int grdev_card_add(grdev_card *card, const char *name); -grdev_card *grdev_card_free(grdev_card *card); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_card*, grdev_card_free); - -/* - * Sessions - */ - -struct grdev_session { - grdev_context *context; - char *name; - char *path; - grdev_event_fn event_fn; - void *userdata; - - unsigned long n_pins; - - Hashmap *card_map; - Hashmap *display_map; - - bool custom : 1; - bool managed : 1; - bool enabled : 1; - bool modified : 1; -}; - -grdev_session *grdev_session_pin(grdev_session *session); -grdev_session *grdev_session_unpin(grdev_session *session); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_session*, grdev_session_unpin); - -/* - * Contexts - */ - -struct grdev_context { - unsigned long ref; - sd_event *event; - sd_bus *sysbus; - - Hashmap *session_map; -}; diff --git a/src/libsystemd-terminal/grdev.c b/src/libsystemd-terminal/grdev.c deleted file mode 100644 index 71f0bd31e7..0000000000 --- a/src/libsystemd-terminal/grdev.c +++ /dev/null @@ -1,1359 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "login-util.h" -#include "macro.h" -#include "util.h" -#include "grdev.h" -#include "grdev-internal.h" - -static void pipe_enable(grdev_pipe *pipe); -static void pipe_disable(grdev_pipe *pipe); -static void card_modified(grdev_card *card); -static void session_frame(grdev_session *session, grdev_display *display); - -/* - * Displays - */ - -static inline grdev_tile *tile_leftmost(grdev_tile *tile) { - if (!tile) - return NULL; - - while (tile->type == GRDEV_TILE_NODE && tile->node.child_list) - tile = tile->node.child_list; - - return tile; -} - -#define TILE_FOREACH(_root, _i) \ - for (_i = tile_leftmost(_root); _i; _i = tile_leftmost(_i->children_by_node_next) ? : _i->parent) - -#define TILE_FOREACH_SAFE(_root, _i, _next) \ - for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->children_by_node_next) ? : _i->parent), true); _i = _next) - -static void tile_link(grdev_tile *tile, grdev_tile *parent) { - grdev_display *display; - grdev_tile *t; - - assert(tile); - assert(!tile->parent); - assert(!tile->display); - assert(parent); - assert(parent->type == GRDEV_TILE_NODE); - - display = parent->display; - - assert(!display || !display->enabled); - - ++parent->node.n_children; - LIST_PREPEND(children_by_node, parent->node.child_list, tile); - tile->parent = parent; - - if (display) { - display->modified = true; - TILE_FOREACH(tile, t) { - t->display = display; - if (t->type == GRDEV_TILE_LEAF) { - ++display->n_leafs; - if (display->enabled) - pipe_enable(t->leaf.pipe); - } - } - } -} - -static void tile_unlink(grdev_tile *tile) { - grdev_tile *parent, *t; - grdev_display *display; - - assert(tile); - - display = tile->display; - parent = tile->parent; - if (!parent) { - assert(!display); - return; - } - - assert(parent->type == GRDEV_TILE_NODE); - assert(parent->display == display); - assert(parent->node.n_children > 0); - - --parent->node.n_children; - LIST_REMOVE(children_by_node, parent->node.child_list, tile); - tile->parent = NULL; - - if (display) { - display->modified = true; - TILE_FOREACH(tile, t) { - t->display = NULL; - if (t->type == GRDEV_TILE_LEAF) { - --display->n_leafs; - t->leaf.pipe->cache = NULL; - pipe_disable(t->leaf.pipe); - } - } - } - - /* Tile trees are driven by leafs. Internal nodes have no owner, thus, - * we must take care to not leave them around. Therefore, whenever we - * unlink any part of a tree, we also destroy the parent, in case it's - * now stale. - * Parents are stale if they have no children and either have no display - * or if they are intermediate nodes (i.e, they have a parent). - * This means, you can easily create trees, but you can never partially - * move or destruct them so far. They're always reduced to minimal form - * if you cut them. This might change later, but so far we didn't need - * partial destruction or the ability to move whole trees. */ - - if (parent->node.n_children < 1 && (parent->parent || !parent->display)) - grdev_tile_free(parent); -} - -static int tile_new(grdev_tile **out) { - _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL; - - assert(out); - - tile = new0(grdev_tile, 1); - if (!tile) - return -ENOMEM; - - tile->type = (unsigned)-1; - - *out = tile; - tile = NULL; - return 0; -} - -int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe) { - _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(pipe, -EINVAL); - assert_return(!pipe->tile, -EINVAL); - - r = tile_new(&tile); - if (r < 0) - return r; - - tile->type = GRDEV_TILE_LEAF; - tile->leaf.pipe = pipe; - - if (out) - *out = tile; - tile = NULL; - return 0; -} - -int grdev_tile_new_node(grdev_tile **out) { - _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL; - int r; - - assert_return(out, -EINVAL); - - r = tile_new(&tile); - if (r < 0) - return r; - - tile->type = GRDEV_TILE_NODE; - - *out = tile; - tile = NULL; - return 0; -} - -grdev_tile *grdev_tile_free(grdev_tile *tile) { - if (!tile) - return NULL; - - tile_unlink(tile); - - switch (tile->type) { - case GRDEV_TILE_LEAF: - assert(!tile->parent); - assert(!tile->display); - assert(tile->leaf.pipe); - - break; - case GRDEV_TILE_NODE: - assert(!tile->parent); - assert(!tile->display); - assert(tile->node.n_children == 0); - - break; - } - - free(tile); - - return NULL; -} - -grdev_display *grdev_find_display(grdev_session *session, const char *name) { - assert_return(session, NULL); - assert_return(name, NULL); - - return hashmap_get(session->display_map, name); -} - -int grdev_display_new(grdev_display **out, grdev_session *session, const char *name) { - _cleanup_(grdev_display_freep) grdev_display *display = NULL; - int r; - - assert(session); - assert(name); - - display = new0(grdev_display, 1); - if (!display) - return -ENOMEM; - - display->session = session; - - display->name = strdup(name); - if (!display->name) - return -ENOMEM; - - r = grdev_tile_new_node(&display->tile); - if (r < 0) - return r; - - display->tile->display = display; - - r = hashmap_put(session->display_map, display->name, display); - if (r < 0) - return r; - - if (out) - *out = display; - display = NULL; - return 0; -} - -grdev_display *grdev_display_free(grdev_display *display) { - if (!display) - return NULL; - - assert(!display->public); - assert(!display->enabled); - assert(!display->modified); - assert(display->n_leafs == 0); - assert(display->n_pipes == 0); - - if (display->name) - hashmap_remove_value(display->session->display_map, display->name, display); - - if (display->tile) { - display->tile->display = NULL; - grdev_tile_free(display->tile); - } - - free(display->pipes); - free(display->name); - free(display); - - return NULL; -} - -void grdev_display_set_userdata(grdev_display *display, void *userdata) { - assert(display); - - display->userdata = userdata; -} - -void *grdev_display_get_userdata(grdev_display *display) { - assert_return(display, NULL); - - return display->userdata; -} - -const char *grdev_display_get_name(grdev_display *display) { - assert_return(display, NULL); - - return display->name; -} - -uint32_t grdev_display_get_width(grdev_display *display) { - assert_return(display, 0); - - return display->width; -} - -uint32_t grdev_display_get_height(grdev_display *display) { - assert_return(display, 0); - - return display->height; -} - -bool grdev_display_is_enabled(grdev_display *display) { - return display && display->enabled; -} - -void grdev_display_enable(grdev_display *display) { - grdev_tile *t; - - assert(display); - - if (!display->enabled) { - display->enabled = true; - TILE_FOREACH(display->tile, t) - if (t->type == GRDEV_TILE_LEAF) - pipe_enable(t->leaf.pipe); - } -} - -void grdev_display_disable(grdev_display *display) { - grdev_tile *t; - - assert(display); - - if (display->enabled) { - display->enabled = false; - TILE_FOREACH(display->tile, t) - if (t->type == GRDEV_TILE_LEAF) - pipe_disable(t->leaf.pipe); - } -} - -const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev) { - grdev_display_cache *cache; - size_t idx; - - assert_return(display, NULL); - assert_return(!display->modified, NULL); - assert_return(display->enabled, NULL); - - if (prev) { - cache = container_of(prev, grdev_display_cache, target); - - assert(cache->pipe); - assert(cache->pipe->tile->display == display); - assert(display->pipes >= cache); - - idx = cache - display->pipes + 1; - } else { - idx = 0; - } - - for (cache = display->pipes + idx; idx < display->n_pipes; ++idx, ++cache) { - grdev_display_target *target; - grdev_pipe *pipe; - grdev_fb *fb; - - pipe = cache->pipe; - target = &cache->target; - - if (!pipe->running || !pipe->enabled) - continue; - - /* find suitable back-buffer */ - if (!pipe->back) { - if (!pipe->vtable->target) - continue; - if (!(fb = pipe->vtable->target(pipe))) - continue; - - assert(fb == pipe->back); - } - - target->front = pipe->front; - target->back = pipe->back; - - return target; - } - - return NULL; -} - -void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target) { - grdev_display_cache *cache; - - assert(display); - assert(!display->modified); - assert(display->enabled); - assert(target); - - cache = container_of(target, grdev_display_cache, target); - - assert(cache->pipe); - assert(cache->pipe->tile->display == display); - - cache->pipe->flip = true; -} - -static void display_cache_apply(grdev_display_cache *c, grdev_tile *l) { - uint32_t x, y, width, height; - grdev_display_target *t; - - assert(c); - assert(l); - assert(l->cache_w >= c->target.width + c->target.x); - assert(l->cache_h >= c->target.height + c->target.y); - - t = &c->target; - - /* rotate child */ - - t->rotate = (t->rotate + l->rotate) & 0x3; - - x = t->x; - y = t->y; - width = t->width; - height = t->height; - - switch (l->rotate) { - case GRDEV_ROTATE_0: - break; - case GRDEV_ROTATE_90: - t->x = l->cache_h - (height + y); - t->y = x; - t->width = height; - t->height = width; - break; - case GRDEV_ROTATE_180: - t->x = l->cache_w - (width + x); - t->y = l->cache_h - (height + y); - break; - case GRDEV_ROTATE_270: - t->x = y; - t->y = l->cache_w - (width + x); - t->width = height; - t->height = width; - break; - } - - /* flip child */ - - t->flip ^= l->flip; - - if (l->flip & GRDEV_FLIP_HORIZONTAL) - t->x = l->cache_w - (t->width + t->x); - if (l->flip & GRDEV_FLIP_VERTICAL) - t->y = l->cache_h - (t->height + t->y); - - /* move child */ - - t->x += l->x; - t->y += l->y; -} - -static void display_cache_targets(grdev_display *display) { - grdev_display_cache *c; - grdev_tile *tile; - - assert(display); - - /* depth-first with children before parent */ - for (tile = tile_leftmost(display->tile); - tile; - tile = tile_leftmost(tile->children_by_node_next) ? : tile->parent) { - if (tile->type == GRDEV_TILE_LEAF) { - grdev_pipe *p; - - /* We're at a leaf and no parent has been cached, yet. - * Copy the pipe information into the target cache and - * update our global pipe-caches if required. */ - - assert(tile->leaf.pipe); - assert(display->n_pipes + 1 <= display->max_pipes); - - p = tile->leaf.pipe; - c = &display->pipes[display->n_pipes++]; - - zero(*c); - c->pipe = p; - c->pipe->cache = c; - c->target.width = p->width; - c->target.height = p->height; - tile->cache_w = p->width; - tile->cache_h = p->height; - - /* all new tiles are incomplete due to geometry changes */ - c->incomplete = true; - - display_cache_apply(c, tile); - } else { - grdev_tile *child, *l; - - /* We're now at a node with all its children already - * computed (depth-first, child before parent). We - * first need to know the size of our tile, then we - * recurse into all leafs and update their cache. */ - - tile->cache_w = 0; - tile->cache_h = 0; - - LIST_FOREACH(children_by_node, child, tile->node.child_list) { - if (child->x + child->cache_w > tile->cache_w) - tile->cache_w = child->x + child->cache_w; - if (child->y + child->cache_h > tile->cache_h) - tile->cache_h = child->y + child->cache_h; - } - - assert(tile->cache_w > 0); - assert(tile->cache_h > 0); - - TILE_FOREACH(tile, l) - if (l->type == GRDEV_TILE_LEAF) - display_cache_apply(l->leaf.pipe->cache, tile); - } - } -} - -static bool display_cache(grdev_display *display) { - grdev_tile *tile; - size_t n; - void *t; - int r; - - assert(display); - - if (!display->modified) - return false; - - display->modified = false; - display->framed = false; - display->n_pipes = 0; - display->width = 0; - display->height = 0; - - if (display->n_leafs < 1) - return false; - - TILE_FOREACH(display->tile, tile) - if (tile->type == GRDEV_TILE_LEAF) - tile->leaf.pipe->cache = NULL; - - if (display->n_leafs > display->max_pipes) { - n = ALIGN_POWER2(display->n_leafs); - if (!n) { - r = -ENOMEM; - goto out; - } - - t = realloc_multiply(display->pipes, sizeof(*display->pipes), n); - if (!t) { - r = -ENOMEM; - goto out; - } - - display->pipes = t; - display->max_pipes = n; - } - - display_cache_targets(display); - display->width = display->tile->cache_w; - display->height = display->tile->cache_h; - - r = 0; - -out: - if (r < 0) - log_debug_errno(r, "grdev: %s/%s: cannot cache pipes: %m", - display->session->name, display->name); - return true; -} - -/* - * Pipes - */ - -grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name) { - assert_return(card, NULL); - assert_return(name, NULL); - - return hashmap_get(card->pipe_map, name); -} - -static int pipe_vsync_fn(sd_event_source *src, uint64_t usec, void *userdata) { - grdev_pipe *pipe = userdata; - - grdev_pipe_frame(pipe); - return 0; -} - -int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs) { - int r; - - assert_return(pipe, -EINVAL); - assert_return(pipe->vtable, -EINVAL); - assert_return(pipe->vtable->free, -EINVAL); - assert_return(pipe->card, -EINVAL); - assert_return(pipe->card->session, -EINVAL); - assert_return(!pipe->cache, -EINVAL); - assert_return(pipe->width > 0, -EINVAL); - assert_return(pipe->height > 0, -EINVAL); - assert_return(pipe->vrefresh > 0, -EINVAL); - assert_return(!pipe->enabled, -EINVAL); - assert_return(!pipe->running, -EINVAL); - assert_return(name, -EINVAL); - - pipe->name = strdup(name); - if (!pipe->name) - return -ENOMEM; - - if (n_fbs > 0) { - pipe->fbs = new0(grdev_fb*, n_fbs); - if (!pipe->fbs) - return -ENOMEM; - - pipe->max_fbs = n_fbs; - } - - r = grdev_tile_new_leaf(&pipe->tile, pipe); - if (r < 0) - return r; - - r = sd_event_add_time(pipe->card->session->context->event, - &pipe->vsync_src, - CLOCK_MONOTONIC, - 0, - 10 * USEC_PER_MSEC, - pipe_vsync_fn, - pipe); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_OFF); - if (r < 0) - return r; - - r = hashmap_put(pipe->card->pipe_map, pipe->name, pipe); - if (r < 0) - return r; - - card_modified(pipe->card); - return 0; -} - -grdev_pipe *grdev_pipe_free(grdev_pipe *pipe) { - grdev_pipe tmp; - - if (!pipe) - return NULL; - - assert(pipe->card); - assert(pipe->vtable); - assert(pipe->vtable->free); - - if (pipe->name) - hashmap_remove_value(pipe->card->pipe_map, pipe->name, pipe); - if (pipe->tile) - tile_unlink(pipe->tile); - - assert(!pipe->cache); - - tmp = *pipe; - pipe->vtable->free(pipe); - - sd_event_source_unref(tmp.vsync_src); - grdev_tile_free(tmp.tile); - card_modified(tmp.card); - free(tmp.fbs); - free(tmp.name); - - return NULL; -} - -static void pipe_enable(grdev_pipe *pipe) { - assert(pipe); - - if (!pipe->enabled) { - pipe->enabled = true; - if (pipe->vtable->enable) - pipe->vtable->enable(pipe); - } -} - -static void pipe_disable(grdev_pipe *pipe) { - assert(pipe); - - if (pipe->enabled) { - pipe->enabled = false; - if (pipe->vtable->disable) - pipe->vtable->disable(pipe); - } -} - -void grdev_pipe_ready(grdev_pipe *pipe, bool running) { - assert(pipe); - - /* grdev_pipe_ready() is used by backends to notify about pipe state - * changed. If a pipe is ready, it can be fully used by us (available, - * enabled and accessible). Backends can disable pipes at any time - * (like for async revocation), but can only enable them from parent - * context. Otherwise, we might call user-callbacks recursively. */ - - if (pipe->running == running) - return; - - pipe->running = running; - - /* runtime events for unused pipes are not interesting */ - if (pipe->cache && pipe->enabled) { - grdev_display *display = pipe->tile->display; - - assert(display); - - if (running) - session_frame(display->session, display); - else - pipe->cache->incomplete = true; - } -} - -void grdev_pipe_frame(grdev_pipe *pipe) { - grdev_display *display; - - assert(pipe); - - /* if pipe is unused, ignore any frame events */ - if (!pipe->cache || !pipe->enabled) - return; - - display = pipe->tile->display; - assert(display); - - grdev_pipe_schedule(pipe, 0); - session_frame(display->session, display); -} - -void grdev_pipe_schedule(grdev_pipe *pipe, uint64_t frames) { - int r; - uint64_t ts; - - if (!frames) { - sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_OFF); - return; - } - - r = sd_event_now(pipe->card->session->context->event, CLOCK_MONOTONIC, &ts); - if (r < 0) - goto error; - - ts += frames * USEC_PER_MSEC * 1000ULL / pipe->vrefresh; - - r = sd_event_source_set_time(pipe->vsync_src, ts); - if (r < 0) - goto error; - - r = sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_ONESHOT); - if (r < 0) - goto error; - - return; - -error: - log_debug_errno(r, "grdev: %s/%s/%s: cannot schedule vsync timer: %m", - pipe->card->session->name, pipe->card->name, pipe->name); -} - -/* - * Cards - */ - -grdev_card *grdev_find_card(grdev_session *session, const char *name) { - assert_return(session, NULL); - assert_return(name, NULL); - - return hashmap_get(session->card_map, name); -} - -int grdev_card_add(grdev_card *card, const char *name) { - int r; - - assert_return(card, -EINVAL); - assert_return(card->vtable, -EINVAL); - assert_return(card->vtable->free, -EINVAL); - assert_return(card->session, -EINVAL); - assert_return(name, -EINVAL); - - card->name = strdup(name); - if (!card->name) - return -ENOMEM; - - card->pipe_map = hashmap_new(&string_hash_ops); - if (!card->pipe_map) - return -ENOMEM; - - r = hashmap_put(card->session->card_map, card->name, card); - if (r < 0) - return r; - - return 0; -} - -grdev_card *grdev_card_free(grdev_card *card) { - grdev_card tmp; - - if (!card) - return NULL; - - assert(!card->enabled); - assert(card->vtable); - assert(card->vtable->free); - - if (card->name) - hashmap_remove_value(card->session->card_map, card->name, card); - - tmp = *card; - card->vtable->free(card); - - assert(hashmap_size(tmp.pipe_map) == 0); - - hashmap_free(tmp.pipe_map); - free(tmp.name); - - return NULL; -} - -static void card_modified(grdev_card *card) { - assert(card); - assert(card->session->n_pins > 0); - - card->modified = true; -} - -static void grdev_card_enable(grdev_card *card) { - assert(card); - - if (!card->enabled) { - card->enabled = true; - if (card->vtable->enable) - card->vtable->enable(card); - } -} - -static void grdev_card_disable(grdev_card *card) { - assert(card); - - if (card->enabled) { - card->enabled = false; - if (card->vtable->disable) - card->vtable->disable(card); - } -} - -/* - * Sessions - */ - -static void session_raise(grdev_session *session, grdev_event *event) { - session->event_fn(session, session->userdata, event); -} - -static void session_raise_display_add(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_ADD, - .display_add = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_raise_display_remove(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_REMOVE, - .display_remove = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_raise_display_change(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_CHANGE, - .display_change = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_raise_display_frame(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_FRAME, - .display_frame = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_add_card(grdev_session *session, grdev_card *card) { - assert(session); - assert(card); - - log_debug("grdev: %s: add card '%s'", session->name, card->name); - - /* Cards are not exposed to users, but managed internally. Cards are - * enabled if the session is enabled, and will track that state. The - * backend can probe the card at any time, but only if enabled. It - * will then add pipes according to hardware state. - * That is, the card may create pipes as soon as we enable it here. */ - - if (session->enabled) - grdev_card_enable(card); -} - -static void session_remove_card(grdev_session *session, grdev_card *card) { - assert(session); - assert(card); - - log_debug("grdev: %s: remove card '%s'", session->name, card->name); - - /* As cards are not exposed, it can never be accessed by outside - * users and we can simply remove it. Disabling the card does not - * necessarily drop all pipes of the card. This is usually deferred - * to card destruction (as pipes are cached as long as FDs remain - * open). Therefore, the card destruction might cause pipes, and thus - * visible displays, to be removed. */ - - grdev_card_disable(card); - grdev_card_free(card); -} - -static void session_add_display(grdev_session *session, grdev_display *display) { - assert(session); - assert(display); - assert(!display->enabled); - - log_debug("grdev: %s: add display '%s'", session->name, display->name); - - /* Displays are the main entity for public API users. We create them - * independent of card backends and they wrap any underlying display - * architecture. Displays are public at all times, thus, may be entered - * by outside users at any time. */ - - display->public = true; - session_raise_display_add(session, display); -} - -static void session_remove_display(grdev_session *session, grdev_display *display) { - assert(session); - assert(display); - - log_debug("grdev: %s: remove display '%s'", session->name, display->name); - - /* Displays are public, so we have to be careful when removing them. - * We first tell users about their removal, disable them and then drop - * them. We now, after the notification, no external access will - * happen. Therefore, we can release the tiles afterwards safely. */ - - if (display->public) { - display->public = false; - session_raise_display_remove(session, display); - } - - grdev_display_disable(display); - grdev_display_free(display); -} - -static void session_change_display(grdev_session *session, grdev_display *display) { - bool changed; - - assert(session); - assert(display); - - changed = display_cache(display); - - if (display->n_leafs == 0) { - session_remove_display(session, display); - } else if (!display->public) { - session_add_display(session, display); - session_frame(session, display); - } else if (changed) { - session_raise_display_change(session, display); - session_frame(session, display); - } else if (display->framed) { - session_frame(session, display); - } -} - -static void session_frame(grdev_session *session, grdev_display *display) { - assert(session); - assert(display); - - display->framed = false; - - if (!display->enabled || !session->enabled) - return; - - if (session->n_pins > 0) - display->framed = true; - else - session_raise_display_frame(session, display); -} - -int grdev_session_new(grdev_session **out, - grdev_context *context, - unsigned int flags, - const char *name, - grdev_event_fn event_fn, - void *userdata) { - _cleanup_(grdev_session_freep) grdev_session *session = NULL; - int r; - - assert(out); - assert(context); - assert(name); - assert(event_fn); - assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL); - assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL); - assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL); - - session = new0(grdev_session, 1); - if (!session) - return -ENOMEM; - - session->context = grdev_context_ref(context); - session->custom = flags & GRDEV_SESSION_CUSTOM; - session->managed = flags & GRDEV_SESSION_MANAGED; - session->event_fn = event_fn; - session->userdata = userdata; - - session->name = strdup(name); - if (!session->name) - return -ENOMEM; - - if (session->managed) { - r = sd_bus_path_encode("/org/freedesktop/login1/session", - session->name, &session->path); - if (r < 0) - return r; - } - - session->card_map = hashmap_new(&string_hash_ops); - if (!session->card_map) - return -ENOMEM; - - session->display_map = hashmap_new(&string_hash_ops); - if (!session->display_map) - return -ENOMEM; - - r = hashmap_put(context->session_map, session->name, session); - if (r < 0) - return r; - - *out = session; - session = NULL; - return 0; -} - -grdev_session *grdev_session_free(grdev_session *session) { - grdev_card *card; - - if (!session) - return NULL; - - grdev_session_disable(session); - - while ((card = hashmap_first(session->card_map))) - session_remove_card(session, card); - - assert(hashmap_size(session->display_map) == 0); - - if (session->name) - hashmap_remove_value(session->context->session_map, session->name, session); - - hashmap_free(session->display_map); - hashmap_free(session->card_map); - session->context = grdev_context_unref(session->context); - free(session->path); - free(session->name); - free(session); - - return NULL; -} - -bool grdev_session_is_enabled(grdev_session *session) { - return session && session->enabled; -} - -void grdev_session_enable(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (!session->enabled) { - session->enabled = true; - HASHMAP_FOREACH(card, session->card_map, iter) - grdev_card_enable(card); - } -} - -void grdev_session_disable(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (session->enabled) { - session->enabled = false; - HASHMAP_FOREACH(card, session->card_map, iter) - grdev_card_disable(card); - } -} - -void grdev_session_commit(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (!session->enabled) - return; - - HASHMAP_FOREACH(card, session->card_map, iter) - if (card->vtable->commit) - card->vtable->commit(card); -} - -void grdev_session_restore(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (!session->enabled) - return; - - HASHMAP_FOREACH(card, session->card_map, iter) - if (card->vtable->restore) - card->vtable->restore(card); -} - -void grdev_session_add_drm(grdev_session *session, struct udev_device *ud) { - grdev_card *card; - dev_t devnum; - int r; - - assert(session); - assert(ud); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return grdev_session_hotplug_drm(session, ud); - - card = grdev_find_drm_card(session, devnum); - if (card) - return; - - r = grdev_drm_card_new(&card, session, ud); - if (r < 0) { - log_debug_errno(r, "grdev: %s: cannot add DRM device for %s: %m", - session->name, udev_device_get_syspath(ud)); - return; - } - - session_add_card(session, card); -} - -void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud) { - grdev_card *card; - dev_t devnum; - - assert(session); - assert(ud); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return grdev_session_hotplug_drm(session, ud); - - card = grdev_find_drm_card(session, devnum); - if (!card) - return; - - session_remove_card(session, card); -} - -void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud) { - grdev_card *card = NULL; - struct udev_device *p; - dev_t devnum; - - assert(session); - assert(ud); - - for (p = ud; p; p = udev_device_get_parent_with_subsystem_devtype(p, "drm", NULL)) { - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - continue; - - card = grdev_find_drm_card(session, devnum); - if (card) - break; - } - - if (!card) - return; - - grdev_drm_card_hotplug(card, ud); -} - -static void session_configure(grdev_session *session) { - grdev_display *display; - grdev_tile *tile; - grdev_card *card; - grdev_pipe *pipe; - Iterator i, j; - int r; - - assert(session); - - /* - * Whenever backends add or remove pipes, we set session->modified and - * require them to pin the session while modifying it. On release, we - * reconfigure the device and re-assign displays to all modified pipes. - * - * So far, we configure each pipe as a separate display. We do not - * support user-configuration, nor have we gotten any reports from - * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until - * we get reports, we keep the logic to a minimum. - */ - - /* create new displays for all unconfigured pipes */ - HASHMAP_FOREACH(card, session->card_map, i) { - if (!card->modified) - continue; - - card->modified = false; - - HASHMAP_FOREACH(pipe, card->pipe_map, j) { - tile = pipe->tile; - if (tile->display) - continue; - - assert(!tile->parent); - - display = grdev_find_display(session, pipe->name); - if (display && display->tile) { - log_debug("grdev: %s/%s: occupied display for pipe %s", - session->name, card->name, pipe->name); - continue; - } else if (!display) { - r = grdev_display_new(&display, session, pipe->name); - if (r < 0) { - log_debug_errno(r, "grdev: %s/%s: cannot create display for pipe %s: %m", - session->name, card->name, pipe->name); - continue; - } - } - - tile_link(pipe->tile, display->tile); - } - } - - /* update displays */ - HASHMAP_FOREACH(display, session->display_map, i) - session_change_display(session, display); -} - -grdev_session *grdev_session_pin(grdev_session *session) { - assert(session); - - ++session->n_pins; - return session; -} - -grdev_session *grdev_session_unpin(grdev_session *session) { - if (!session) - return NULL; - - assert(session->n_pins > 0); - - if (--session->n_pins == 0) - session_configure(session); - - return NULL; -} - -/* - * Contexts - */ - -int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) { - _cleanup_(grdev_context_unrefp) grdev_context *context = NULL; - - assert_return(out, -EINVAL); - assert_return(event, -EINVAL); - - context = new0(grdev_context, 1); - if (!context) - return -ENOMEM; - - context->ref = 1; - context->event = sd_event_ref(event); - - if (sysbus) - context->sysbus = sd_bus_ref(sysbus); - - context->session_map = hashmap_new(&string_hash_ops); - if (!context->session_map) - return -ENOMEM; - - *out = context; - context = NULL; - return 0; -} - -static void context_cleanup(grdev_context *context) { - assert(hashmap_size(context->session_map) == 0); - - hashmap_free(context->session_map); - context->sysbus = sd_bus_unref(context->sysbus); - context->event = sd_event_unref(context->event); - free(context); -} - -grdev_context *grdev_context_ref(grdev_context *context) { - assert_return(context, NULL); - assert_return(context->ref > 0, NULL); - - ++context->ref; - return context; -} - -grdev_context *grdev_context_unref(grdev_context *context) { - if (!context) - return NULL; - - assert_return(context->ref > 0, NULL); - - if (--context->ref == 0) - context_cleanup(context); - - return NULL; -} diff --git a/src/libsystemd-terminal/grdev.h b/src/libsystemd-terminal/grdev.h deleted file mode 100644 index 110d24e6d5..0000000000 --- a/src/libsystemd-terminal/grdev.h +++ /dev/null @@ -1,199 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Graphics Devices - * The grdev layer provides generic access to graphics devices. The device - * types are hidden in the implementation and exported in a generic way. The - * grdev_session object forms the base layer. It loads, configures and prepares - * any graphics devices associated with that session. Each session is totally - * independent of other sessions and can be controlled separately. - * The target devices on a session are called display. A display always - * corresponds to a real display regardless how many pipes are needed to drive - * that display. That is, an exported display might internally be created out - * of arbitrary combinations of target pipes. However, this is meant as - * implementation detail and API users must never assume details below the - * display-level. That is, a display is the most low-level object exported. - * Therefore, pipe-configuration and any low-level modesetting is hidden from - * the public API. It is provided by the implementation, and it is the - * implementation that decides how pipes are driven. - * - * The API users are free to ignore specific displays or combine them to create - * larger screens. This often requires user-configuration so is dictated by - * policy. The underlying pipe-configuration might be affected by these - * high-level policies, but is never directly controlled by those. That means, - * depending on the displays you use, it might affect how underlying resources - * are assigned. However, users can never directly apply policies to the pipes, - * but only to displays. In case specific hardware needs quirks on the pipe - * level, we support that via hwdb, not via public user configuration. - * - * Right now, displays are limited to rgb32 memory-mapped framebuffers on the - * primary plane. However, the grdev implementation can be easily extended to - * allow more powerful access (including hardware-acceleration for 2D and 3D - * compositing). So far, this wasn't needed so it is not exposed. - */ - -#pragma once - -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "util.h" - -typedef struct grdev_fb grdev_fb; -typedef struct grdev_display_target grdev_display_target; -typedef struct grdev_display grdev_display; - -typedef struct grdev_event grdev_event; -typedef struct grdev_session grdev_session; -typedef struct grdev_context grdev_context; - -enum { - /* clockwise rotation; we treat this is abelian group Z4 with ADD */ - GRDEV_ROTATE_0 = 0, - GRDEV_ROTATE_90 = 1, - GRDEV_ROTATE_180 = 2, - GRDEV_ROTATE_270 = 3, -}; - -enum { - /* flip states; we treat this as abelian group V4 with XOR */ - GRDEV_FLIP_NONE = 0x0, - GRDEV_FLIP_HORIZONTAL = 0x1, - GRDEV_FLIP_VERTICAL = 0x2, -}; - -/* - * Displays - */ - -struct grdev_fb { - uint32_t width; - uint32_t height; - uint32_t format; - int32_t strides[4]; - void *maps[4]; - - union { - void *ptr; - uint64_t u64; - } data; - - void (*free_fn) (void *ptr); -}; - -struct grdev_display_target { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; - unsigned int rotate; - unsigned int flip; - grdev_fb *front; - grdev_fb *back; -}; - -void grdev_display_set_userdata(grdev_display *display, void *userdata); -void *grdev_display_get_userdata(grdev_display *display); - -const char *grdev_display_get_name(grdev_display *display); -uint32_t grdev_display_get_width(grdev_display *display); -uint32_t grdev_display_get_height(grdev_display *display); - -bool grdev_display_is_enabled(grdev_display *display); -void grdev_display_enable(grdev_display *display); -void grdev_display_disable(grdev_display *display); - -const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev); -void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target); - -#define GRDEV_DISPLAY_FOREACH_TARGET(_display, _t) \ - for ((_t) = grdev_display_next_target((_display), NULL); \ - (_t); \ - (_t) = grdev_display_next_target((_display), (_t))) - -/* - * Events - */ - -enum { - GRDEV_EVENT_DISPLAY_ADD, - GRDEV_EVENT_DISPLAY_REMOVE, - GRDEV_EVENT_DISPLAY_CHANGE, - GRDEV_EVENT_DISPLAY_FRAME, -}; - -typedef void (*grdev_event_fn) (grdev_session *session, void *userdata, grdev_event *ev); - -struct grdev_event { - unsigned int type; - union { - struct { - grdev_display *display; - } display_add, display_remove, display_change; - - struct { - grdev_display *display; - } display_frame; - }; -}; - -/* - * Sessions - */ - -enum { - GRDEV_SESSION_CUSTOM = (1 << 0), - GRDEV_SESSION_MANAGED = (1 << 1), -}; - -int grdev_session_new(grdev_session **out, - grdev_context *context, - unsigned int flags, - const char *name, - grdev_event_fn event_fn, - void *userdata); -grdev_session *grdev_session_free(grdev_session *session); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_session*, grdev_session_free); - -bool grdev_session_is_enabled(grdev_session *session); -void grdev_session_enable(grdev_session *session); -void grdev_session_disable(grdev_session *session); - -void grdev_session_commit(grdev_session *session); -void grdev_session_restore(grdev_session *session); - -void grdev_session_add_drm(grdev_session *session, struct udev_device *ud); -void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud); -void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud); - -/* - * Contexts - */ - -int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus); -grdev_context *grdev_context_ref(grdev_context *context); -grdev_context *grdev_context_unref(grdev_context *context); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_context*, grdev_context_unref); diff --git a/src/libsystemd-terminal/idev-evdev.c b/src/libsystemd-terminal/idev-evdev.c deleted file mode 100644 index f1a18b91d3..0000000000 --- a/src/libsystemd-terminal/idev-evdev.c +++ /dev/null @@ -1,859 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <fcntl.h> -#include <libevdev/libevdev.h> -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "macro.h" -#include "util.h" -#include "bus-util.h" -#include "idev.h" -#include "idev-internal.h" - -typedef struct idev_evdev idev_evdev; -typedef struct unmanaged_evdev unmanaged_evdev; -typedef struct managed_evdev managed_evdev; - -struct idev_evdev { - idev_element element; - struct libevdev *evdev; - int fd; - sd_event_source *fd_src; - sd_event_source *idle_src; - - bool unsync : 1; /* not in-sync with kernel */ - bool resync : 1; /* re-syncing with kernel */ - bool running : 1; -}; - -struct unmanaged_evdev { - idev_evdev evdev; - char *devnode; -}; - -struct managed_evdev { - idev_evdev evdev; - dev_t devnum; - sd_bus_slot *slot_take_device; - - bool requested : 1; /* TakeDevice() was sent */ - bool acquired : 1; /* TakeDevice() was successful */ -}; - -#define idev_evdev_from_element(_e) container_of((_e), idev_evdev, element) -#define unmanaged_evdev_from_element(_e) \ - container_of(idev_evdev_from_element(_e), unmanaged_evdev, evdev) -#define managed_evdev_from_element(_e) \ - container_of(idev_evdev_from_element(_e), managed_evdev, evdev) - -#define IDEV_EVDEV_INIT(_vtable, _session) ((idev_evdev){ \ - .element = IDEV_ELEMENT_INIT((_vtable), (_session)), \ - .fd = -1, \ - }) - -#define IDEV_EVDEV_NAME_MAX (8 + DECIMAL_STR_MAX(unsigned) * 2) - -static const idev_element_vtable unmanaged_evdev_vtable; -static const idev_element_vtable managed_evdev_vtable; - -static int idev_evdev_resume(idev_evdev *evdev, int dev_fd); -static void idev_evdev_pause(idev_evdev *evdev, bool release); - -/* - * Virtual Evdev Element - * The virtual evdev element is the base class of all other evdev elements. It - * uses libevdev to access the kernel evdev API. It supports asynchronous - * access revocation, re-syncing if events got dropped and more. - * This element cannot be used by itself. There must be a wrapper around it - * which opens a file-descriptor and passes it to the virtual evdev element. - */ - -static void idev_evdev_name(char *out, dev_t devnum) { - /* @out must be at least of size IDEV_EVDEV_NAME_MAX */ - sprintf(out, "evdev/%u:%u", major(devnum), minor(devnum)); -} - -static int idev_evdev_feed_resync(idev_evdev *evdev) { - idev_data data = { - .type = IDEV_DATA_RESYNC, - .resync = evdev->resync, - }; - - return idev_element_feed(&evdev->element, &data); -} - -static int idev_evdev_feed_evdev(idev_evdev *evdev, struct input_event *event) { - idev_data data = { - .type = IDEV_DATA_EVDEV, - .resync = evdev->resync, - .evdev = { - .event = *event, - }, - }; - - return idev_element_feed(&evdev->element, &data); -} - -static void idev_evdev_hup(idev_evdev *evdev) { - /* - * On HUP, we close the current fd via idev_evdev_pause(). This drops - * the event-sources from the main-loop and effectively puts the - * element asleep. If the HUP is part of a hotplug-event, a following - * udev-notification will destroy the element. Otherwise, the HUP is - * either result of access-revokation or a serious error. - * For unmanaged devices, we should never receive HUP (except for - * unplug-events). But if we do, something went seriously wrong and we - * shouldn't try to be clever. - * Instead, we simply stay asleep and wait for the device to be - * disabled and then re-enabled (or closed and re-opened). This will - * re-open the device node and restart the device. - * For managed devices, a HUP usually means our device-access was - * revoked. In that case, we simply put the device asleep and wait for - * logind to notify us once the device is alive again. logind also - * passes us a new fd. Hence, we don't have to re-enable the device. - * - * Long story short: The only thing we have to do here, is close() the - * file-descriptor and remove it from the main-loop. Everything else is - * handled via additional events we receive. - */ - - idev_evdev_pause(evdev, true); -} - -static int idev_evdev_io(idev_evdev *evdev) { - idev_element *e = &evdev->element; - struct input_event ev; - unsigned int flags; - int r, error = 0; - - /* - * Read input-events via libevdev until the input-queue is drained. In - * case we're disabled, don't do anything. The input-queue might - * overflow, but we don't care as we have to resync after wake-up, - * anyway. - * TODO: libevdev should give us a hint how many events to read. We - * really want to avoid starvation, so we shouldn't read forever in - * case we cannot keep up with the kernel. - * TODO: Make sure libevdev always reports SYN_DROPPED to us, regardless - * whether any event was synced afterwards. - */ - - flags = LIBEVDEV_READ_FLAG_NORMAL; - while (e->enabled) { - if (evdev->unsync) { - /* immediately resync, even if in sync right now */ - evdev->unsync = false; - evdev->resync = false; - flags = LIBEVDEV_READ_FLAG_NORMAL; - r = libevdev_next_event(evdev->evdev, flags | LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); - if (r < 0 && r != -EAGAIN) { - r = 0; - goto error; - } else if (r != LIBEVDEV_READ_STATUS_SYNC) { - log_debug("idev-evdev: %s/%s: cannot force resync: %d", - e->session->name, e->name, r); - } - } else { - r = libevdev_next_event(evdev->evdev, flags, &ev); - } - - if (evdev->resync && r == -EAGAIN) { - /* end of re-sync */ - evdev->resync = false; - flags = LIBEVDEV_READ_FLAG_NORMAL; - } else if (r == -EAGAIN) { - /* no data available */ - break; - } else if (r < 0) { - /* read error */ - goto error; - } else if (r == LIBEVDEV_READ_STATUS_SYNC) { - if (evdev->resync) { - /* sync-event */ - r = idev_evdev_feed_evdev(evdev, &ev); - if (r != 0) { - error = r; - break; - } - } else { - /* start of sync */ - evdev->resync = true; - flags = LIBEVDEV_READ_FLAG_SYNC; - r = idev_evdev_feed_resync(evdev); - if (r != 0) { - error = r; - break; - } - } - } else { - /* normal event */ - r = idev_evdev_feed_evdev(evdev, &ev); - if (r != 0) { - error = r; - break; - } - } - } - - if (error < 0) - log_debug_errno(error, "idev-evdev: %s/%s: error on data event: %m", - e->session->name, e->name); - return error; - -error: - idev_evdev_hup(evdev); - return 0; /* idev_evdev_hup() handles the error so discard it */ -} - -static int idev_evdev_event_fn(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - idev_evdev *evdev = userdata; - - /* fetch data as long as EPOLLIN is signalled */ - if (revents & EPOLLIN) - return idev_evdev_io(evdev); - - if (revents & (EPOLLHUP | EPOLLERR)) - idev_evdev_hup(evdev); - - return 0; -} - -static int idev_evdev_idle_fn(sd_event_source *s, void *userdata) { - idev_evdev *evdev = userdata; - - /* - * The idle-event is raised whenever we have to re-sync the libevdev - * state from the kernel. We simply call into idev_evdev_io() which - * flushes the state and re-syncs it if @unsync is set. - * State has to be synced whenever our view of the kernel device is - * out of date. This is the case when we open the device, if the - * kernel's receive buffer overflows, or on other exceptional - * situations. Events during re-syncs must be forwarded to the upper - * layers so they can update their view of the device. However, such - * events must only be handled passively, as they might be out-of-order - * and/or re-ordered. Therefore, we mark them as 'sync' events. - */ - - if (!evdev->unsync) - return 0; - - return idev_evdev_io(evdev); -} - -static void idev_evdev_destroy(idev_evdev *evdev) { - assert(evdev); - assert(evdev->fd < 0); - - libevdev_free(evdev->evdev); - evdev->evdev = NULL; -} - -static void idev_evdev_enable(idev_evdev *evdev) { - assert(evdev); - assert(evdev->fd_src); - assert(evdev->idle_src); - - if (evdev->running) - return; - if (evdev->fd < 0 || evdev->element.n_open < 1 || !evdev->element.enabled) - return; - - evdev->running = true; - sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_ON); - sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_ONESHOT); -} - -static void idev_evdev_disable(idev_evdev *evdev) { - assert(evdev); - assert(evdev->fd_src); - assert(evdev->idle_src); - - if (!evdev->running) - return; - - evdev->running = false; - idev_evdev_feed_resync(evdev); - sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_OFF); - sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_OFF); -} - -static int idev_evdev_resume(idev_evdev *evdev, int dev_fd) { - idev_element *e = &evdev->element; - _cleanup_close_ int fd = dev_fd; - int r, flags; - - if (fd < 0 || evdev->fd == fd) { - fd = -1; - idev_evdev_enable(evdev); - return 0; - } - - idev_evdev_pause(evdev, true); - log_debug("idev-evdev: %s/%s: resume", e->session->name, e->name); - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = fd_cloexec(fd, true); - if (r < 0) - return r; - - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -errno; - - flags &= O_ACCMODE; - if (flags == O_WRONLY) - return -EACCES; - - evdev->element.readable = true; - evdev->element.writable = !(flags & O_RDONLY); - - /* - * TODO: We *MUST* re-sync the device so we get a delta of the changed - * state while we didn't read events from the device. This works just - * fine with libevdev_change_fd(), however, libevdev_new_from_fd() (or - * libevdev_set_fd()) don't pass us events for the initial device - * state. So even if we force a re-sync, we will not get the delta for - * the initial device state. - * We really need to fix libevdev to support that! - */ - if (evdev->evdev) - r = libevdev_change_fd(evdev->evdev, fd); - else - r = libevdev_new_from_fd(fd, &evdev->evdev); - - if (r < 0) - return r; - - r = sd_event_add_io(e->session->context->event, - &evdev->fd_src, - fd, - EPOLLHUP | EPOLLERR | EPOLLIN, - idev_evdev_event_fn, - evdev); - if (r < 0) - return r; - - r = sd_event_add_defer(e->session->context->event, - &evdev->idle_src, - idev_evdev_idle_fn, - evdev); - if (r < 0) { - evdev->fd_src = sd_event_source_unref(evdev->fd_src); - return r; - } - - sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_OFF); - sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_OFF); - - evdev->unsync = true; - evdev->fd = fd; - fd = -1; - - idev_evdev_enable(evdev); - return 0; -} - -static void idev_evdev_pause(idev_evdev *evdev, bool release) { - idev_element *e = &evdev->element; - - if (evdev->fd < 0) - return; - - log_debug("idev-evdev: %s/%s: pause", e->session->name, e->name); - - idev_evdev_disable(evdev); - if (release) { - evdev->idle_src = sd_event_source_unref(evdev->idle_src); - evdev->fd_src = sd_event_source_unref(evdev->fd_src); - evdev->fd = safe_close(evdev->fd); - } -} - -/* - * Unmanaged Evdev Element - * The unmanaged evdev element opens the evdev node for a given input device - * directly (/dev/input/eventX) and thus needs sufficient privileges. It opens - * the device only if we really require it and releases it as soon as we're - * disabled or closed. - * The unmanaged element can be used in all situations where you have direct - * access to input device nodes. Unlike managed evdev elements, it can be used - * outside of user sessions and in emergency situations where logind is not - * available. - */ - -static void unmanaged_evdev_resume(idev_element *e) { - unmanaged_evdev *eu = unmanaged_evdev_from_element(e); - int r, fd; - - /* - * Unmanaged devices can be acquired on-demand. Therefore, don't - * acquire it unless someone opened the device *and* we're enabled. - */ - if (e->n_open < 1 || !e->enabled) - return; - - fd = eu->evdev.fd; - if (fd < 0) { - fd = open(eu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - if (errno != EACCES && errno != EPERM) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot open node %s: %m", - e->session->name, e->name, eu->devnode); - return; - } - - fd = open(eu->devnode, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot open node %s: %m", - e->session->name, e->name, eu->devnode); - return; - } - - e->readable = true; - e->writable = false; - } else { - e->readable = true; - e->writable = true; - } - } - - r = idev_evdev_resume(&eu->evdev, fd); - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot resume: %m", - e->session->name, e->name); -} - -static void unmanaged_evdev_pause(idev_element *e) { - unmanaged_evdev *eu = unmanaged_evdev_from_element(e); - - /* - * Release the device if the device is disabled or there is no-one who - * opened it. This guarantees we stay only available if we're opened - * *and* enabled. - */ - - idev_evdev_pause(&eu->evdev, true); -} - -static int unmanaged_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { - _cleanup_(idev_element_freep) idev_element *e = NULL; - char name[IDEV_EVDEV_NAME_MAX]; - unmanaged_evdev *eu; - const char *devnode; - dev_t devnum; - int r; - - assert_return(s, -EINVAL); - assert_return(ud, -EINVAL); - - devnode = udev_device_get_devnode(ud); - devnum = udev_device_get_devnum(ud); - if (!devnode || devnum == 0) - return -ENODEV; - - idev_evdev_name(name, devnum); - - eu = new0(unmanaged_evdev, 1); - if (!eu) - return -ENOMEM; - - e = &eu->evdev.element; - eu->evdev = IDEV_EVDEV_INIT(&unmanaged_evdev_vtable, s); - - eu->devnode = strdup(devnode); - if (!eu->devnode) - return -ENOMEM; - - r = idev_element_add(e, name); - if (r < 0) - return r; - - if (out) - *out = e; - e = NULL; - return 0; -} - -static void unmanaged_evdev_free(idev_element *e) { - unmanaged_evdev *eu = unmanaged_evdev_from_element(e); - - idev_evdev_destroy(&eu->evdev); - free(eu->devnode); - free(eu); -} - -static const idev_element_vtable unmanaged_evdev_vtable = { - .free = unmanaged_evdev_free, - .enable = unmanaged_evdev_resume, - .disable = unmanaged_evdev_pause, - .open = unmanaged_evdev_resume, - .close = unmanaged_evdev_pause, -}; - -/* - * Managed Evdev Element - * The managed evdev element uses systemd-logind to acquire evdev devices. This - * means, we do not open the device node /dev/input/eventX directly. Instead, - * logind passes us a file-descriptor whenever our session is activated. Thus, - * we don't need access to the device node directly. - * Furthermore, whenever the session is put asleep, logind revokes the - * file-descriptor so we loose access to the device. - * Managed evdev elements should be preferred over unmanaged elements whenever - * you run inside a user session with exclusive device access. - */ - -static int managed_evdev_take_device_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - managed_evdev *em = userdata; - idev_element *e = &em->evdev.element; - idev_session *s = e->session; - int r, paused, fd; - - em->slot_take_device = sd_bus_slot_unref(em->slot_take_device); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("idev-evdev: %s/%s: TakeDevice failed: %s: %s", - s->name, e->name, error->name, error->message); - return 0; - } - - em->acquired = true; - - r = sd_bus_message_read(reply, "hb", &fd, &paused); - if (r < 0) { - log_debug("idev-evdev: %s/%s: erroneous TakeDevice reply", s->name, e->name); - return 0; - } - - /* If the device is paused, ignore it; we will get the next fd via - * ResumeDevice signals. */ - if (paused) - return 0; - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot duplicate evdev fd: %m", s->name, e->name); - return 0; - } - - r = idev_evdev_resume(&em->evdev, fd); - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot resume: %m", - s->name, e->name); - - return 0; -} - -static void managed_evdev_enable(idev_element *e) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - idev_context *c = s->context; - int r; - - /* - * Acquiring managed devices is heavy, so do it only once we're - * enabled *and* opened by someone. - */ - if (e->n_open < 1 || !e->enabled) - return; - - /* bail out if already pending */ - if (em->requested) - return; - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - s->path, - "org.freedesktop.login1.Session", - "TakeDevice"); - if (r < 0) - goto error; - - r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); - if (r < 0) - goto error; - - r = sd_bus_call_async(c->sysbus, - &em->slot_take_device, - m, - managed_evdev_take_device_fn, - em, - 0); - if (r < 0) - goto error; - - em->requested = true; - return; - -error: - log_debug_errno(r, "idev-evdev: %s/%s: cannot send TakeDevice request: %m", - s->name, e->name); -} - -static void managed_evdev_disable(idev_element *e) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - idev_context *c = s->context; - int r; - - /* - * Releasing managed devices is heavy. Once acquired, we get - * notifications for sleep/wake-up events, so there's no reason to - * release it if disabled but opened. However, if a device is closed, - * we release it immediately as we don't care for sleep/wake-up events - * then (even if we're actually enabled). - */ - - idev_evdev_pause(&em->evdev, false); - - if (e->n_open > 0 || !em->requested) - return; - - /* - * If TakeDevice() is pending or was successful, make sure to - * release the device again. We don't care for return-values, - * so send it without waiting or callbacks. - * If a failed TakeDevice() is pending, but someone else took - * the device on the same bus-connection, we might incorrectly - * release their device. This is an unlikely race, though. - * Furthermore, you really shouldn't have two users of the - * controller-API on the same session, on the same devices, *AND* on - * the same bus-connection. So we don't care for that race.. - */ - - idev_evdev_pause(&em->evdev, true); - em->requested = false; - - if (!em->acquired && !em->slot_take_device) - return; - - em->slot_take_device = sd_bus_slot_unref(em->slot_take_device); - em->acquired = false; - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - s->path, - "org.freedesktop.login1.Session", - "ReleaseDevice"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); - if (r >= 0) - r = sd_bus_send(c->sysbus, m, NULL); - } - - if (r < 0 && r != -ENOTCONN) - log_debug_errno(r, "idev-evdev: %s/%s: cannot send ReleaseDevice: %m", - s->name, e->name); -} - -static void managed_evdev_resume(idev_element *e, int fd) { - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - int r; - - /* - * We get ResumeDevice signals whenever logind resumed a previously - * paused device. The arguments contain the major/minor number of the - * related device and a new file-descriptor for the freshly opened - * device-node. We take the file-descriptor and immediately resume the - * device. - */ - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot duplicate evdev fd: %m", - s->name, e->name); - return; - } - - r = idev_evdev_resume(&em->evdev, fd); - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot resume: %m", - s->name, e->name); - - return; -} - -static void managed_evdev_pause(idev_element *e, const char *mode) { - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - idev_context *c = s->context; - int r; - - /* - * We get PauseDevice() signals from logind whenever a device we - * requested was, or is about to be, paused. Arguments are major/minor - * number of the device and the mode of the operation. - * We treat it as asynchronous access-revocation (as if we got HUP on - * the device fd). Note that we might have already treated the HUP - * event via EPOLLHUP, whichever comes first. - * - * @mode can be one of the following: - * "pause": The device is about to be paused. We must react - * immediately and respond with PauseDeviceComplete(). Once - * we replied, logind will pause the device. Note that - * logind might apply any kind of timeout and force pause - * the device if we don't respond in a timely manner. In - * this case, we will receive a second PauseDevice event - * with @mode set to "force" (or similar). - * "force": The device was disabled forecfully by logind. Access is - * already revoked. This is just an asynchronous - * notification so we can put the device asleep (in case - * we didn't already notice the access revocation). - * "gone": This is like "force" but is sent if the device was - * paused due to a device-removal event. - * - * We always handle PauseDevice signals as "force" as we properly - * support asynchronous access revocation, anyway. But in case logind - * sent mode "pause", we also call PauseDeviceComplete() to immediately - * acknowledge the request. - */ - - idev_evdev_pause(&em->evdev, true); - - if (streq(mode, "pause")) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - - /* - * Sending PauseDeviceComplete() is racy if logind triggers the - * timeout. That is, if we take too long and logind pauses the - * device by sending a forced PauseDevice, our - * PauseDeviceComplete call will be stray. That's fine, though. - * logind ignores such stray calls. Only if logind also sent a - * further PauseDevice() signal, it might match our call - * incorrectly to the newer PauseDevice(). That's fine, too, as - * we handle that event asynchronously, anyway. Therefore, - * whatever happens, we're fine. Yay! - */ - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - s->path, - "org.freedesktop.login1.Session", - "PauseDeviceComplete"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); - if (r >= 0) - r = sd_bus_send(c->sysbus, m, NULL); - } - - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot send PauseDeviceComplete: %m", - s->name, e->name); - } -} - -static int managed_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { - _cleanup_(idev_element_freep) idev_element *e = NULL; - char name[IDEV_EVDEV_NAME_MAX]; - managed_evdev *em; - dev_t devnum; - int r; - - assert_return(s, -EINVAL); - assert_return(s->managed, -EINVAL); - assert_return(s->context->sysbus, -EINVAL); - assert_return(ud, -EINVAL); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return -ENODEV; - - idev_evdev_name(name, devnum); - - em = new0(managed_evdev, 1); - if (!em) - return -ENOMEM; - - e = &em->evdev.element; - em->evdev = IDEV_EVDEV_INIT(&managed_evdev_vtable, s); - em->devnum = devnum; - - r = idev_element_add(e, name); - if (r < 0) - return r; - - if (out) - *out = e; - e = NULL; - return 0; -} - -static void managed_evdev_free(idev_element *e) { - managed_evdev *em = managed_evdev_from_element(e); - - idev_evdev_destroy(&em->evdev); - free(em); -} - -static const idev_element_vtable managed_evdev_vtable = { - .free = managed_evdev_free, - .enable = managed_evdev_enable, - .disable = managed_evdev_disable, - .open = managed_evdev_enable, - .close = managed_evdev_disable, - .resume = managed_evdev_resume, - .pause = managed_evdev_pause, -}; - -/* - * Generic Constructor - * Instead of relying on the caller to choose between managed and unmanaged - * evdev devices, the idev_evdev_new() constructor does that for you (by - * looking at s->managed). - */ - -bool idev_is_evdev(idev_element *e) { - return e && (e->vtable == &unmanaged_evdev_vtable || - e->vtable == &managed_evdev_vtable); -} - -idev_element *idev_find_evdev(idev_session *s, dev_t devnum) { - char name[IDEV_EVDEV_NAME_MAX]; - - assert_return(s, NULL); - assert_return(devnum != 0, NULL); - - idev_evdev_name(name, devnum); - return idev_find_element(s, name); -} - -int idev_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { - assert_return(s, -EINVAL); - assert_return(ud, -EINVAL); - - return s->managed ? managed_evdev_new(out, s, ud) : unmanaged_evdev_new(out, s, ud); -} diff --git a/src/libsystemd-terminal/idev-internal.h b/src/libsystemd-terminal/idev-internal.h deleted file mode 100644 index a02a16c408..0000000000 --- a/src/libsystemd-terminal/idev-internal.h +++ /dev/null @@ -1,188 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <inttypes.h> -#include <libudev.h> -#include <linux/input.h> -#include <stdbool.h> -#include <stdlib.h> -#include <xkbcommon/xkbcommon.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "list.h" -#include "util.h" -#include "idev.h" - -typedef struct idev_link idev_link; -typedef struct idev_device_vtable idev_device_vtable; -typedef struct idev_element idev_element; -typedef struct idev_element_vtable idev_element_vtable; - -/* - * Evdev Elements - */ - -bool idev_is_evdev(idev_element *e); -idev_element *idev_find_evdev(idev_session *s, dev_t devnum); -int idev_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud); - -/* - * Keyboard Devices - */ - -bool idev_is_keyboard(idev_device *d); -idev_device *idev_find_keyboard(idev_session *s, const char *name); -int idev_keyboard_new(idev_device **out, idev_session *s, const char *name); - -/* - * Element Links - */ - -struct idev_link { - /* element-to-device connection */ - LIST_FIELDS(idev_link, links_by_element); - idev_element *element; - - /* device-to-element connection */ - LIST_FIELDS(idev_link, links_by_device); - idev_device *device; -}; - -/* - * Devices - */ - -struct idev_device_vtable { - void (*free) (idev_device *d); - void (*attach) (idev_device *d, idev_link *l); - void (*detach) (idev_device *d, idev_link *l); - int (*feed) (idev_device *d, idev_data *data); -}; - -struct idev_device { - const idev_device_vtable *vtable; - idev_session *session; - char *name; - - LIST_HEAD(idev_link, links); - - bool public : 1; - bool enabled : 1; -}; - -#define IDEV_DEVICE_INIT(_vtable, _session) ((idev_device){ \ - .vtable = (_vtable), \ - .session = (_session), \ - }) - -idev_device *idev_find_device(idev_session *s, const char *name); - -int idev_device_add(idev_device *d, const char *name); -idev_device *idev_device_free(idev_device *d); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_device*, idev_device_free); - -int idev_device_feed(idev_device *d, idev_data *data); -void idev_device_feedback(idev_device *d, idev_data *data); - -/* - * Elements - */ - -struct idev_element_vtable { - void (*free) (idev_element *e); - void (*enable) (idev_element *e); - void (*disable) (idev_element *e); - void (*open) (idev_element *e); - void (*close) (idev_element *e); - void (*resume) (idev_element *e, int fd); - void (*pause) (idev_element *e, const char *mode); - void (*feedback) (idev_element *e, idev_data *data); -}; - -struct idev_element { - const idev_element_vtable *vtable; - idev_session *session; - unsigned long n_open; - char *name; - - LIST_HEAD(idev_link, links); - - bool enabled : 1; - bool readable : 1; - bool writable : 1; -}; - -#define IDEV_ELEMENT_INIT(_vtable, _session) ((idev_element){ \ - .vtable = (_vtable), \ - .session = (_session), \ - }) - -idev_element *idev_find_element(idev_session *s, const char *name); - -int idev_element_add(idev_element *e, const char *name); -idev_element *idev_element_free(idev_element *e); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_element*, idev_element_free); - -int idev_element_feed(idev_element *e, idev_data *data); -void idev_element_feedback(idev_element *e, idev_data *data); - -/* - * Sessions - */ - -struct idev_session { - idev_context *context; - char *name; - char *path; - sd_bus_slot *slot_resume_device; - sd_bus_slot *slot_pause_device; - - Hashmap *element_map; - Hashmap *device_map; - - idev_event_fn event_fn; - void *userdata; - - bool custom : 1; - bool managed : 1; - bool enabled : 1; -}; - -idev_session *idev_find_session(idev_context *c, const char *name); -int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data); - -/* - * Contexts - */ - -struct idev_context { - unsigned long ref; - sd_event *event; - sd_bus *sysbus; - - Hashmap *session_map; - Hashmap *data_map; -}; diff --git a/src/libsystemd-terminal/idev-keyboard.c b/src/libsystemd-terminal/idev-keyboard.c deleted file mode 100644 index 93f49e9458..0000000000 --- a/src/libsystemd-terminal/idev-keyboard.c +++ /dev/null @@ -1,1159 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <stdbool.h> -#include <stdlib.h> -#include <xkbcommon/xkbcommon.h> -#include <xkbcommon/xkbcommon-compose.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "macro.h" -#include "util.h" -#include "bus-util.h" -#include "idev.h" -#include "idev-internal.h" -#include "term-internal.h" - -typedef struct kbdtbl kbdtbl; -typedef struct kbdmap kbdmap; -typedef struct kbdctx kbdctx; -typedef struct idev_keyboard idev_keyboard; - -struct kbdtbl { - unsigned long ref; - struct xkb_compose_table *xkb_compose_table; -}; - -struct kbdmap { - unsigned long ref; - struct xkb_keymap *xkb_keymap; - xkb_mod_index_t modmap[IDEV_KBDMOD_CNT]; - xkb_led_index_t ledmap[IDEV_KBDLED_CNT]; -}; - -struct kbdctx { - unsigned long ref; - idev_context *context; - struct xkb_context *xkb_context; - struct kbdmap *kbdmap; - struct kbdtbl *kbdtbl; - - sd_bus_slot *slot_locale_props_changed; - sd_bus_slot *slot_locale_get_all; - - char *locale_lang; - char *locale_x11_model; - char *locale_x11_layout; - char *locale_x11_variant; - char *locale_x11_options; - char *last_x11_model; - char *last_x11_layout; - char *last_x11_variant; - char *last_x11_options; -}; - -struct idev_keyboard { - idev_device device; - kbdctx *kbdctx; - kbdmap *kbdmap; - kbdtbl *kbdtbl; - - struct xkb_state *xkb_state; - struct xkb_compose_state *xkb_compose; - - usec_t repeat_delay; - usec_t repeat_rate; - sd_event_source *repeat_timer; - - uint32_t n_syms; - idev_data evdata; - idev_data repdata; - uint32_t *compose_res; - - bool repeating : 1; -}; - -#define keyboard_from_device(_d) container_of((_d), idev_keyboard, device) - -#define KBDCTX_KEY "keyboard.context" /* hashmap key for global kbdctx */ -#define KBDXKB_SHIFT (8) /* xkb shifts evdev key-codes by 8 */ -#define KBDKEY_UP (0) /* KEY UP event value */ -#define KBDKEY_DOWN (1) /* KEY DOWN event value */ -#define KBDKEY_REPEAT (2) /* KEY REPEAT event value */ - -static const idev_device_vtable keyboard_vtable; - -static int keyboard_update_kbdmap(idev_keyboard *k); -static int keyboard_update_kbdtbl(idev_keyboard *k); - -/* - * Keyboard Compose Tables - */ - -static kbdtbl *kbdtbl_ref(kbdtbl *kt) { - if (kt) { - assert_return(kt->ref > 0, NULL); - ++kt->ref; - } - - return kt; -} - -static kbdtbl *kbdtbl_unref(kbdtbl *kt) { - if (!kt) - return NULL; - - assert_return(kt->ref > 0, NULL); - - if (--kt->ref > 0) - return NULL; - - xkb_compose_table_unref(kt->xkb_compose_table); - free(kt); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(kbdtbl*, kbdtbl_unref); - -static int kbdtbl_new_from_locale(kbdtbl **out, kbdctx *kc, const char *locale) { - _cleanup_(kbdtbl_unrefp) kbdtbl *kt = NULL; - - assert_return(out, -EINVAL); - assert_return(locale, -EINVAL); - - kt = new0(kbdtbl, 1); - if (!kt) - return -ENOMEM; - - kt->ref = 1; - - kt->xkb_compose_table = xkb_compose_table_new_from_locale(kc->xkb_context, - locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); - if (!kt->xkb_compose_table) - return errno > 0 ? -errno : -EFAULT; - - *out = kt; - kt = NULL; - return 0; -} - -/* - * Keyboard Keymaps - */ - -static const char * const kbdmap_modmap[IDEV_KBDMOD_CNT] = { - [IDEV_KBDMOD_IDX_SHIFT] = XKB_MOD_NAME_SHIFT, - [IDEV_KBDMOD_IDX_CTRL] = XKB_MOD_NAME_CTRL, - [IDEV_KBDMOD_IDX_ALT] = XKB_MOD_NAME_ALT, - [IDEV_KBDMOD_IDX_LINUX] = XKB_MOD_NAME_LOGO, - [IDEV_KBDMOD_IDX_CAPS] = XKB_MOD_NAME_CAPS, -}; - -static const char * const kbdmap_ledmap[IDEV_KBDLED_CNT] = { - [IDEV_KBDLED_IDX_NUM] = XKB_LED_NAME_NUM, - [IDEV_KBDLED_IDX_CAPS] = XKB_LED_NAME_CAPS, - [IDEV_KBDLED_IDX_SCROLL] = XKB_LED_NAME_SCROLL, -}; - -static kbdmap *kbdmap_ref(kbdmap *km) { - assert_return(km, NULL); - assert_return(km->ref > 0, NULL); - - ++km->ref; - return km; -} - -static kbdmap *kbdmap_unref(kbdmap *km) { - if (!km) - return NULL; - - assert_return(km->ref > 0, NULL); - - if (--km->ref > 0) - return NULL; - - xkb_keymap_unref(km->xkb_keymap); - free(km); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(kbdmap*, kbdmap_unref); - -static int kbdmap_new_from_names(kbdmap **out, - kbdctx *kc, - const char *model, - const char *layout, - const char *variant, - const char *options) { - _cleanup_(kbdmap_unrefp) kbdmap *km = NULL; - struct xkb_rule_names rmlvo = { }; - unsigned int i; - - assert_return(out, -EINVAL); - - km = new0(kbdmap, 1); - if (!km) - return -ENOMEM; - - km->ref = 1; - - rmlvo.rules = "evdev"; - rmlvo.model = model; - rmlvo.layout = layout; - rmlvo.variant = variant; - rmlvo.options = options; - - errno = 0; - km->xkb_keymap = xkb_keymap_new_from_names(kc->xkb_context, &rmlvo, 0); - if (!km->xkb_keymap) - return errno > 0 ? -errno : -EFAULT; - - for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { - const char *t = kbdmap_modmap[i]; - - if (t) - km->modmap[i] = xkb_keymap_mod_get_index(km->xkb_keymap, t); - else - km->modmap[i] = XKB_MOD_INVALID; - } - - for (i = 0; i < IDEV_KBDLED_CNT; ++i) { - const char *t = kbdmap_ledmap[i]; - - if (t) - km->ledmap[i] = xkb_keymap_led_get_index(km->xkb_keymap, t); - else - km->ledmap[i] = XKB_LED_INVALID; - } - - *out = km; - km = NULL; - return 0; -} - -/* - * Keyboard Context - */ - -static int kbdctx_refresh_compose_table(kbdctx *kc, const char *lang) { - kbdtbl *kt; - idev_session *s; - idev_device *d; - Iterator i, j; - int r; - - if (!lang) - lang = "C"; - - if (streq_ptr(kc->locale_lang, lang)) - return 0; - - r = free_and_strdup(&kc->locale_lang, lang); - if (r < 0) - return r; - - log_debug("idev-keyboard: new default compose table: [ %s ]", lang); - - r = kbdtbl_new_from_locale(&kt, kc, lang); - if (r < 0) { - /* TODO: We need to catch the case where no compose-file is - * available. xkb doesn't tell us so far.. so we must not treat - * it as a hard-failure but just continue. Preferably, we want - * xkb to tell us exactly whether compilation failed or whether - * there is no compose file available for this locale. */ - log_debug_errno(r, "idev-keyboard: cannot load compose-table for '%s': %m", - lang); - r = 0; - kt = NULL; - } - - kbdtbl_unref(kc->kbdtbl); - kc->kbdtbl = kt; - - HASHMAP_FOREACH(s, kc->context->session_map, i) - HASHMAP_FOREACH(d, s->device_map, j) - if (idev_is_keyboard(d)) - keyboard_update_kbdtbl(keyboard_from_device(d)); - - return 0; -} - -static void move_str(char **dest, char **src) { - free(*dest); - *dest = *src; - *src = NULL; -} - -static int kbdctx_refresh_keymap(kbdctx *kc) { - idev_session *s; - idev_device *d; - Iterator i, j; - kbdmap *km; - int r; - - if (kc->kbdmap && - streq_ptr(kc->locale_x11_model, kc->last_x11_model) && - streq_ptr(kc->locale_x11_layout, kc->last_x11_layout) && - streq_ptr(kc->locale_x11_variant, kc->last_x11_variant) && - streq_ptr(kc->locale_x11_options, kc->last_x11_options)) - return 0 ; - - move_str(&kc->last_x11_model, &kc->locale_x11_model); - move_str(&kc->last_x11_layout, &kc->locale_x11_layout); - move_str(&kc->last_x11_variant, &kc->locale_x11_variant); - move_str(&kc->last_x11_options, &kc->locale_x11_options); - - log_debug("idev-keyboard: new default keymap: [%s / %s / %s / %s]", - kc->last_x11_model, kc->last_x11_layout, kc->last_x11_variant, kc->last_x11_options); - - /* TODO: add a fallback keymap that's compiled-in */ - r = kbdmap_new_from_names(&km, kc, kc->last_x11_model, kc->last_x11_layout, - kc->last_x11_variant, kc->last_x11_options); - if (r < 0) - return log_debug_errno(r, "idev-keyboard: cannot create keymap from locale1: %m"); - - kbdmap_unref(kc->kbdmap); - kc->kbdmap = km; - - HASHMAP_FOREACH(s, kc->context->session_map, i) - HASHMAP_FOREACH(d, s->device_map, j) - if (idev_is_keyboard(d)) - keyboard_update_kbdmap(keyboard_from_device(d)); - - return 0; -} - -static int kbdctx_set_locale(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - kbdctx *kc = userdata; - const char *s, *ctype = NULL, *lang = NULL; - int r; - - r = sd_bus_message_enter_container(m, 'a', "s"); - if (r < 0) - goto error; - - while ((r = sd_bus_message_read(m, "s", &s)) > 0) { - if (!ctype) - ctype = startswith(s, "LC_CTYPE="); - if (!lang) - lang = startswith(s, "LANG="); - } - - if (r < 0) - goto error; - - r = sd_bus_message_exit_container(m); - if (r < 0) - goto error; - - kbdctx_refresh_compose_table(kc, ctype ? : lang); - r = 0; - -error: - if (r < 0) - log_debug_errno(r, "idev-keyboard: cannot parse locale property from locale1: %m"); - - return r; -} - -static const struct bus_properties_map kbdctx_locale_map[] = { - { "Locale", "as", kbdctx_set_locale, 0 }, - { "X11Model", "s", NULL, offsetof(kbdctx, locale_x11_model) }, - { "X11Layout", "s", NULL, offsetof(kbdctx, locale_x11_layout) }, - { "X11Variant", "s", NULL, offsetof(kbdctx, locale_x11_variant) }, - { "X11Options", "s", NULL, offsetof(kbdctx, locale_x11_options) }, - { }, -}; - -static int kbdctx_locale_get_all_fn(sd_bus_message *m, - void *userdata, - sd_bus_error *ret_err) { - kbdctx *kc = userdata; - int r; - - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - - if (sd_bus_message_is_method_error(m, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(m); - - log_debug("idev-keyboard: GetAll() on locale1 failed: %s: %s", - error->name, error->message); - return 0; - } - - r = bus_message_map_all_properties(m, kbdctx_locale_map, kc); - if (r < 0) { - log_debug("idev-keyboard: erroneous GetAll() reply from locale1"); - return 0; - } - - kbdctx_refresh_keymap(kc); - return 0; -} - -static int kbdctx_query_locale(kbdctx *kc) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - - r = sd_bus_message_new_method_call(kc->context->sysbus, - &m, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - "org.freedesktop.DBus.Properties", - "GetAll"); - if (r < 0) - goto error; - - r = sd_bus_message_append(m, "s", "org.freedesktop.locale1"); - if (r < 0) - goto error; - - r = sd_bus_call_async(kc->context->sysbus, - &kc->slot_locale_get_all, - m, - kbdctx_locale_get_all_fn, - kc, - 0); - if (r < 0) - goto error; - - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: cannot send GetAll to locale1: %m"); -} - -static int kbdctx_locale_props_changed_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_err) { - kbdctx *kc = userdata; - int r; - - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - - /* skip interface name */ - r = sd_bus_message_skip(signal, "s"); - if (r < 0) - goto error; - - r = bus_message_map_properties_changed(signal, kbdctx_locale_map, kc); - if (r < 0) - goto error; - - if (r > 0) { - r = kbdctx_query_locale(kc); - if (r < 0) - return r; - } - - kbdctx_refresh_keymap(kc); - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: cannot handle PropertiesChanged from locale1: %m"); -} - -static int kbdctx_setup_bus(kbdctx *kc) { - int r; - - r = sd_bus_add_match(kc->context->sysbus, - &kc->slot_locale_props_changed, - "type='signal'," - "sender='org.freedesktop.locale1'," - "interface='org.freedesktop.DBus.Properties'," - "member='PropertiesChanged'," - "path='/org/freedesktop/locale1'", - kbdctx_locale_props_changed_fn, - kc); - if (r < 0) - return log_debug_errno(r, "idev-keyboard: cannot setup locale1 link: %m"); - - return kbdctx_query_locale(kc); -} - -static void kbdctx_log_fn(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { - char buf[LINE_MAX]; - int sd_lvl; - - if (lvl >= XKB_LOG_LEVEL_DEBUG) - sd_lvl = LOG_DEBUG; - else if (lvl >= XKB_LOG_LEVEL_INFO) - sd_lvl = LOG_INFO; - else if (lvl >= XKB_LOG_LEVEL_WARNING) - sd_lvl = LOG_INFO; /* most XKB warnings really are informational */ - else - /* XKB_LOG_LEVEL_ERROR and worse */ - sd_lvl = LOG_ERR; - - snprintf(buf, sizeof(buf), "idev-xkb: %s", format); - log_internalv(sd_lvl, 0, __FILE__, __LINE__, __func__, buf, args); -} - -static kbdctx *kbdctx_ref(kbdctx *kc) { - assert_return(kc, NULL); - assert_return(kc->ref > 0, NULL); - - ++kc->ref; - return kc; -} - -static kbdctx *kbdctx_unref(kbdctx *kc) { - if (!kc) - return NULL; - - assert_return(kc->ref > 0, NULL); - - if (--kc->ref > 0) - return NULL; - - free(kc->last_x11_options); - free(kc->last_x11_variant); - free(kc->last_x11_layout); - free(kc->last_x11_model); - free(kc->locale_x11_options); - free(kc->locale_x11_variant); - free(kc->locale_x11_layout); - free(kc->locale_x11_model); - free(kc->locale_lang); - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - kc->slot_locale_props_changed = sd_bus_slot_unref(kc->slot_locale_props_changed); - kc->kbdtbl = kbdtbl_unref(kc->kbdtbl); - kc->kbdmap = kbdmap_unref(kc->kbdmap); - xkb_context_unref(kc->xkb_context); - hashmap_remove_value(kc->context->data_map, KBDCTX_KEY, kc); - free(kc); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(kbdctx*, kbdctx_unref); - -static int kbdctx_new(kbdctx **out, idev_context *c) { - _cleanup_(kbdctx_unrefp) kbdctx *kc = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(c, -EINVAL); - - kc = new0(kbdctx, 1); - if (!kc) - return -ENOMEM; - - kc->ref = 1; - kc->context = c; - - errno = 0; - kc->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!kc->xkb_context) - return errno > 0 ? -errno : -EFAULT; - - xkb_context_set_log_fn(kc->xkb_context, kbdctx_log_fn); - xkb_context_set_log_level(kc->xkb_context, XKB_LOG_LEVEL_DEBUG); - - r = kbdctx_refresh_keymap(kc); - if (r < 0) - return r; - - r = kbdctx_refresh_compose_table(kc, NULL); - if (r < 0) - return r; - - if (c->sysbus) { - r = kbdctx_setup_bus(kc); - if (r < 0) - return r; - } - - r = hashmap_put(c->data_map, KBDCTX_KEY, kc); - if (r < 0) - return r; - - *out = kc; - kc = NULL; - return 0; -} - -static int get_kbdctx(idev_context *c, kbdctx **out) { - kbdctx *kc; - - assert_return(c, -EINVAL); - assert_return(out, -EINVAL); - - kc = hashmap_get(c->data_map, KBDCTX_KEY); - if (kc) { - *out = kbdctx_ref(kc); - return 0; - } - - return kbdctx_new(out, c); -} - -/* - * Keyboard Devices - */ - -bool idev_is_keyboard(idev_device *d) { - return d && d->vtable == &keyboard_vtable; -} - -idev_device *idev_find_keyboard(idev_session *s, const char *name) { - char *kname; - - assert_return(s, NULL); - assert_return(name, NULL); - - kname = strjoina("keyboard/", name); - return hashmap_get(s->device_map, kname); -} - -static int keyboard_raise_data(idev_keyboard *k, idev_data *data) { - idev_device *d = &k->device; - int r; - - r = idev_session_raise_device_data(d->session, d, data); - if (r < 0) - log_debug_errno(r, "idev-keyboard: %s/%s: error while raising data event: %m", - d->session->name, d->name); - - return r; -} - -static int keyboard_resize_bufs(idev_keyboard *k, uint32_t n_syms) { - uint32_t *t; - - if (n_syms <= k->n_syms) - return 0; - - t = realloc(k->compose_res, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->compose_res = t; - - t = realloc(k->evdata.keyboard.keysyms, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->evdata.keyboard.keysyms = t; - - t = realloc(k->evdata.keyboard.codepoints, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->evdata.keyboard.codepoints = t; - - t = realloc(k->repdata.keyboard.keysyms, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->repdata.keyboard.keysyms = t; - - t = realloc(k->repdata.keyboard.codepoints, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->repdata.keyboard.codepoints = t; - - k->n_syms = n_syms; - return 0; -} - -static unsigned int keyboard_read_compose(idev_keyboard *k, const xkb_keysym_t **out) { - _cleanup_free_ char *t = NULL; - term_utf8 u8 = { }; - char buf[256], *p; - size_t flen = 0; - int i, r; - - r = xkb_compose_state_get_utf8(k->xkb_compose, buf, sizeof(buf)); - if (r >= (int)sizeof(buf)) { - t = malloc(r + 1); - if (!t) - return 0; - - xkb_compose_state_get_utf8(k->xkb_compose, t, r + 1); - p = t; - } else { - p = buf; - } - - for (i = 0; i < r; ++i) { - uint32_t *ucs; - size_t len, j; - - len = term_utf8_decode(&u8, &ucs, p[i]); - if (len > 0) { - r = keyboard_resize_bufs(k, flen + len); - if (r < 0) - return 0; - - for (j = 0; j < len; ++j) - k->compose_res[flen++] = ucs[j]; - } - } - - *out = k->compose_res; - return flen; -} - -static void keyboard_arm(idev_keyboard *k, usec_t usecs) { - int r; - - if (usecs != 0) { - usecs += now(CLOCK_MONOTONIC); - r = sd_event_source_set_time(k->repeat_timer, usecs); - if (r >= 0) - sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_ONESHOT); - } else { - sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_OFF); - } -} - -static int keyboard_repeat_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) { - idev_keyboard *k = userdata; - - /* never feed REPEAT keys into COMPOSE */ - - keyboard_arm(k, k->repeat_rate); - return keyboard_raise_data(k, &k->repdata); -} - -int idev_keyboard_new(idev_device **out, idev_session *s, const char *name) { - _cleanup_(idev_device_freep) idev_device *d = NULL; - idev_keyboard *k; - char *kname; - int r; - - assert_return(out, -EINVAL); - assert_return(s, -EINVAL); - assert_return(name, -EINVAL); - - k = new0(idev_keyboard, 1); - if (!k) - return -ENOMEM; - - d = &k->device; - k->device = IDEV_DEVICE_INIT(&keyboard_vtable, s); - k->repeat_delay = 250 * USEC_PER_MSEC; - k->repeat_rate = 30 * USEC_PER_MSEC; - - /* TODO: add key-repeat configuration */ - - r = get_kbdctx(s->context, &k->kbdctx); - if (r < 0) - return r; - - r = keyboard_update_kbdmap(k); - if (r < 0) - return r; - - r = keyboard_update_kbdtbl(k); - if (r < 0) - return r; - - r = keyboard_resize_bufs(k, 8); - if (r < 0) - return r; - - r = sd_event_add_time(s->context->event, - &k->repeat_timer, - CLOCK_MONOTONIC, - 0, - 10 * USEC_PER_MSEC, - keyboard_repeat_timer_fn, - k); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_OFF); - if (r < 0) - return r; - - kname = strjoina("keyboard/", name); - r = idev_device_add(d, kname); - if (r < 0) - return r; - - if (out) - *out = d; - d = NULL; - return 0; -} - -static void keyboard_free(idev_device *d) { - idev_keyboard *k = keyboard_from_device(d); - - xkb_compose_state_unref(k->xkb_compose); - xkb_state_unref(k->xkb_state); - free(k->repdata.keyboard.codepoints); - free(k->repdata.keyboard.keysyms); - free(k->evdata.keyboard.codepoints); - free(k->evdata.keyboard.keysyms); - free(k->compose_res); - k->repeat_timer = sd_event_source_unref(k->repeat_timer); - k->kbdtbl = kbdtbl_unref(k->kbdtbl); - k->kbdmap = kbdmap_unref(k->kbdmap); - k->kbdctx = kbdctx_unref(k->kbdctx); - free(k); -} - -static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_syms, const uint32_t *syms) { - xkb_layout_index_t n_lo, lo; - xkb_level_index_t lv; - struct xkb_keymap *keymap; - const xkb_keysym_t *s; - int num; - - if (n_syms == 1 && syms[0] < 128 && syms[0] > 0) - return syms[0]; - - keymap = xkb_state_get_keymap(state); - n_lo = xkb_keymap_num_layouts_for_key(keymap, code + KBDXKB_SHIFT); - - for (lo = 0; lo < n_lo; ++lo) { - lv = xkb_state_key_get_level(state, code + KBDXKB_SHIFT, lo); - num = xkb_keymap_key_get_syms_by_level(keymap, code + KBDXKB_SHIFT, lo, lv, &s); - if (num == 1 && s[0] < 128 && s[0] > 0) - return s[0]; - } - - return -1; -} - -static int keyboard_fill(idev_keyboard *k, - idev_data *dst, - bool resync, - uint16_t code, - uint32_t value, - uint32_t n_syms, - const uint32_t *keysyms) { - idev_data_keyboard *kev; - uint32_t i; - int r; - - assert(dst == &k->evdata || dst == &k->repdata); - - r = keyboard_resize_bufs(k, n_syms); - if (r < 0) - return r; - - dst->type = IDEV_DATA_KEYBOARD; - dst->resync = resync; - kev = &dst->keyboard; - kev->ascii = guess_ascii(k->xkb_state, code, n_syms, keysyms); - kev->value = value; - kev->keycode = code; - kev->mods = 0; - kev->consumed_mods = 0; - kev->n_syms = n_syms; - memcpy(kev->keysyms, keysyms, sizeof(*keysyms) * n_syms); - - for (i = 0; i < n_syms; ++i) { - kev->codepoints[i] = xkb_keysym_to_utf32(keysyms[i]); - if (!kev->codepoints[i]) - kev->codepoints[i] = 0xffffffffUL; - } - - for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { - if (k->kbdmap->modmap[i] == XKB_MOD_INVALID) - continue; - - r = xkb_state_mod_index_is_active(k->xkb_state, k->kbdmap->modmap[i], XKB_STATE_MODS_EFFECTIVE); - if (r > 0) - kev->mods |= 1 << i; - - r = xkb_state_mod_index_is_consumed(k->xkb_state, code + KBDXKB_SHIFT, k->kbdmap->modmap[i]); - if (r > 0) - kev->consumed_mods |= 1 << i; - } - - return 0; -} - -static void keyboard_repeat(idev_keyboard *k) { - idev_data *evdata = &k->evdata; - idev_data *repdata = &k->repdata; - idev_data_keyboard *evkbd = &evdata->keyboard; - idev_data_keyboard *repkbd = &repdata->keyboard; - const xkb_keysym_t *keysyms; - idev_device *d = &k->device; - bool repeats; - int r, num; - - if (evdata->resync) { - /* - * We received a re-sync event. During re-sync, any number of - * key-events may have been lost and sync-events may be - * re-ordered. Always disable key-repeat for those events. Any - * following event will trigger it again. - */ - - k->repeating = false; - keyboard_arm(k, 0); - return; - } - - repeats = xkb_keymap_key_repeats(k->kbdmap->xkb_keymap, evkbd->keycode + KBDXKB_SHIFT); - - if (k->repeating && repkbd->keycode == evkbd->keycode) { - /* - * We received an event for the key we currently repeat. If it - * was released, stop key-repeat. Otherwise, ignore the event. - */ - - if (evkbd->value == KBDKEY_UP) { - k->repeating = false; - keyboard_arm(k, 0); - } - } else if (evkbd->value == KBDKEY_DOWN && repeats) { - /* - * We received a key-down event for a key that repeats. The - * previous condition caught keys we already repeat, so we know - * this is a different key or no key-repeat is running. Start - * new key-repeat. - */ - - errno = 0; - num = xkb_state_key_get_syms(k->xkb_state, evkbd->keycode + KBDXKB_SHIFT, &keysyms); - if (num < 0) - r = errno > 0 ? errno : -EFAULT; - else - r = keyboard_fill(k, repdata, false, evkbd->keycode, KBDKEY_REPEAT, num, keysyms); - - if (r < 0) { - log_debug_errno(r, "idev-keyboard: %s/%s: cannot set key-repeat: %m", - d->session->name, d->name); - k->repeating = false; - keyboard_arm(k, 0); - } else { - k->repeating = true; - keyboard_arm(k, k->repeat_delay); - } - } else if (k->repeating && !repeats) { - /* - * We received an event for a key that does not repeat, but we - * currently repeat a previously received key. The new key is - * usually a modifier, but might be any kind of key. In this - * case, we continue repeating the old key, but update the - * symbols according to the new state. - */ - - errno = 0; - num = xkb_state_key_get_syms(k->xkb_state, repkbd->keycode + KBDXKB_SHIFT, &keysyms); - if (num < 0) - r = errno > 0 ? errno : -EFAULT; - else - r = keyboard_fill(k, repdata, false, repkbd->keycode, KBDKEY_REPEAT, num, keysyms); - - if (r < 0) { - log_debug_errno(r, "idev-keyboard: %s/%s: cannot update key-repeat: %m", - d->session->name, d->name); - k->repeating = false; - keyboard_arm(k, 0); - } - } -} - -static int keyboard_feed_evdev(idev_keyboard *k, idev_data *data) { - struct input_event *ev = &data->evdev.event; - enum xkb_state_component compch; - enum xkb_compose_status cstatus; - const xkb_keysym_t *keysyms; - idev_device *d = &k->device; - int num, r; - - if (ev->type != EV_KEY || ev->value > KBDKEY_DOWN) - return 0; - - /* TODO: We should audit xkb-actions, whether they need @resync as - * flag. Most actions should just be executed, however, there might - * be actions that depend on modifier-orders. Those should be - * suppressed. */ - - num = xkb_state_key_get_syms(k->xkb_state, ev->code + KBDXKB_SHIFT, &keysyms); - compch = xkb_state_update_key(k->xkb_state, ev->code + KBDXKB_SHIFT, ev->value); - - if (compch & XKB_STATE_LEDS) { - /* TODO: update LEDs */ - } - - if (num < 0) { - r = num; - goto error; - } - - if (k->xkb_compose && ev->value == KBDKEY_DOWN) { - if (num == 1 && !data->resync) { - xkb_compose_state_feed(k->xkb_compose, keysyms[0]); - cstatus = xkb_compose_state_get_status(k->xkb_compose); - } else { - cstatus = XKB_COMPOSE_CANCELLED; - } - - switch (cstatus) { - case XKB_COMPOSE_NOTHING: - /* keep produced keysyms and forward unchanged */ - break; - case XKB_COMPOSE_COMPOSING: - /* consumed by compose-state, drop keysym */ - keysyms = NULL; - num = 0; - break; - case XKB_COMPOSE_COMPOSED: - /* compose-state produced sth, replace keysym */ - num = keyboard_read_compose(k, &keysyms); - xkb_compose_state_reset(k->xkb_compose); - break; - case XKB_COMPOSE_CANCELLED: - /* canceled compose, reset, forward cancellation sym */ - xkb_compose_state_reset(k->xkb_compose); - break; - } - } else if (k->xkb_compose && - num == 1 && - keysyms[0] == XKB_KEY_Multi_key && - !data->resync && - ev->value == KBDKEY_UP) { - /* Reset compose state on Multi-Key UP events. This effectively - * requires you to hold the key during the whole sequence. I - * think it's pretty handy to avoid accidental - * Compose-sequences, but this may break Compose for disabled - * people. We really need to make this opional! (TODO) */ - xkb_compose_state_reset(k->xkb_compose); - } - - if (ev->value == KBDKEY_UP) { - /* never produce keysyms for UP */ - keysyms = NULL; - num = 0; - } - - r = keyboard_fill(k, &k->evdata, data->resync, ev->code, ev->value, num, keysyms); - if (r < 0) - goto error; - - keyboard_repeat(k); - return keyboard_raise_data(k, &k->evdata); - -error: - log_debug_errno(r, "idev-keyboard: %s/%s: cannot handle event: %m", - d->session->name, d->name); - k->repeating = false; - keyboard_arm(k, 0); - return 0; -} - -static int keyboard_feed(idev_device *d, idev_data *data) { - idev_keyboard *k = keyboard_from_device(d); - - switch (data->type) { - case IDEV_DATA_RESYNC: - /* - * If the underlying device is re-synced, key-events might be - * sent re-ordered. Thus, we don't know which key was pressed - * last. Key-repeat might get confused, hence, disable it - * during re-syncs. The first following event will enable it - * again. - */ - - k->repeating = false; - keyboard_arm(k, 0); - return 0; - case IDEV_DATA_EVDEV: - return keyboard_feed_evdev(k, data); - default: - return 0; - } -} - -static int keyboard_update_kbdmap(idev_keyboard *k) { - idev_device *d = &k->device; - struct xkb_state *state; - kbdmap *km; - int r; - - assert(k); - - km = k->kbdctx->kbdmap; - if (km == k->kbdmap) - return 0; - - errno = 0; - state = xkb_state_new(km->xkb_keymap); - if (!state) { - r = errno > 0 ? -errno : -EFAULT; - goto error; - } - - kbdmap_unref(k->kbdmap); - k->kbdmap = kbdmap_ref(km); - xkb_state_unref(k->xkb_state); - k->xkb_state = state; - - /* TODO: On state-change, we should trigger a resync so the whole - * event-state is flushed into the new xkb-state. libevdev currently - * does not support that, though. */ - - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: %s/%s: cannot adopt new keymap: %m", - d->session->name, d->name); -} - -static int keyboard_update_kbdtbl(idev_keyboard *k) { - idev_device *d = &k->device; - struct xkb_compose_state *compose = NULL; - kbdtbl *kt; - int r; - - assert(k); - - kt = k->kbdctx->kbdtbl; - if (kt == k->kbdtbl) - return 0; - - if (kt) { - errno = 0; - compose = xkb_compose_state_new(kt->xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); - if (!compose) { - r = errno > 0 ? -errno : -EFAULT; - goto error; - } - } - - kbdtbl_unref(k->kbdtbl); - k->kbdtbl = kbdtbl_ref(kt); - xkb_compose_state_unref(k->xkb_compose); - k->xkb_compose = compose; - - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: %s/%s: cannot adopt new compose table: %m", - d->session->name, d->name); -} - -static const idev_device_vtable keyboard_vtable = { - .free = keyboard_free, - .feed = keyboard_feed, -}; diff --git a/src/libsystemd-terminal/idev.c b/src/libsystemd-terminal/idev.c deleted file mode 100644 index b92a393b69..0000000000 --- a/src/libsystemd-terminal/idev.c +++ /dev/null @@ -1,799 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "login-util.h" -#include "macro.h" -#include "util.h" -#include "idev.h" -#include "idev-internal.h" - -static void element_open(idev_element *e); -static void element_close(idev_element *e); - -/* - * Devices - */ - -idev_device *idev_find_device(idev_session *s, const char *name) { - assert_return(s, NULL); - assert_return(name, NULL); - - return hashmap_get(s->device_map, name); -} - -int idev_device_add(idev_device *d, const char *name) { - int r; - - assert_return(d, -EINVAL); - assert_return(d->vtable, -EINVAL); - assert_return(d->session, -EINVAL); - assert_return(name, -EINVAL); - - d->name = strdup(name); - if (!d->name) - return -ENOMEM; - - r = hashmap_put(d->session->device_map, d->name, d); - if (r < 0) - return r; - - return 0; -} - -idev_device *idev_device_free(idev_device *d) { - idev_device tmp; - - if (!d) - return NULL; - - assert(!d->enabled); - assert(!d->public); - assert(!d->links); - assert(d->vtable); - assert(d->vtable->free); - - if (d->name) - hashmap_remove_value(d->session->device_map, d->name, d); - - tmp = *d; - d->vtable->free(d); - - free(tmp.name); - - return NULL; -} - -int idev_device_feed(idev_device *d, idev_data *data) { - assert(d); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - if (d->vtable->feed) - return d->vtable->feed(d, data); - else - return 0; -} - -void idev_device_feedback(idev_device *d, idev_data *data) { - idev_link *l; - - assert(d); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - LIST_FOREACH(links_by_device, l, d->links) - idev_element_feedback(l->element, data); -} - -static void device_attach(idev_device *d, idev_link *l) { - assert(d); - assert(l); - - if (d->vtable->attach) - d->vtable->attach(d, l); - - if (d->enabled) - element_open(l->element); -} - -static void device_detach(idev_device *d, idev_link *l) { - assert(d); - assert(l); - - if (d->enabled) - element_close(l->element); - - if (d->vtable->detach) - d->vtable->detach(d, l); -} - -void idev_device_enable(idev_device *d) { - idev_link *l; - - assert(d); - - if (!d->enabled) { - d->enabled = true; - LIST_FOREACH(links_by_device, l, d->links) - element_open(l->element); - } -} - -void idev_device_disable(idev_device *d) { - idev_link *l; - - assert(d); - - if (d->enabled) { - d->enabled = false; - LIST_FOREACH(links_by_device, l, d->links) - element_close(l->element); - } -} - -/* - * Elements - */ - -idev_element *idev_find_element(idev_session *s, const char *name) { - assert_return(s, NULL); - assert_return(name, NULL); - - return hashmap_get(s->element_map, name); -} - -int idev_element_add(idev_element *e, const char *name) { - int r; - - assert_return(e, -EINVAL); - assert_return(e->vtable, -EINVAL); - assert_return(e->session, -EINVAL); - assert_return(name, -EINVAL); - - e->name = strdup(name); - if (!e->name) - return -ENOMEM; - - r = hashmap_put(e->session->element_map, e->name, e); - if (r < 0) - return r; - - return 0; -} - -idev_element *idev_element_free(idev_element *e) { - idev_element tmp; - - if (!e) - return NULL; - - assert(!e->enabled); - assert(!e->links); - assert(e->n_open == 0); - assert(e->vtable); - assert(e->vtable->free); - - if (e->name) - hashmap_remove_value(e->session->element_map, e->name, e); - - tmp = *e; - e->vtable->free(e); - - free(tmp.name); - - return NULL; -} - -int idev_element_feed(idev_element *e, idev_data *data) { - int r, error = 0; - idev_link *l; - - assert(e); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - LIST_FOREACH(links_by_element, l, e->links) { - r = idev_device_feed(l->device, data); - if (r != 0) - error = r; - } - - return error; -} - -void idev_element_feedback(idev_element *e, idev_data *data) { - assert(e); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - if (e->vtable->feedback) - e->vtable->feedback(e, data); -} - -static void element_open(idev_element *e) { - assert(e); - - if (e->n_open++ == 0 && e->vtable->open) - e->vtable->open(e); -} - -static void element_close(idev_element *e) { - assert(e); - assert(e->n_open > 0); - - if (--e->n_open == 0 && e->vtable->close) - e->vtable->close(e); -} - -static void element_enable(idev_element *e) { - assert(e); - - if (!e->enabled) { - e->enabled = true; - if (e->vtable->enable) - e->vtable->enable(e); - } -} - -static void element_disable(idev_element *e) { - assert(e); - - if (e->enabled) { - e->enabled = false; - if (e->vtable->disable) - e->vtable->disable(e); - } -} - -static void element_resume(idev_element *e, int fd) { - assert(e); - assert(fd >= 0); - - if (e->vtable->resume) - e->vtable->resume(e, fd); -} - -static void element_pause(idev_element *e, const char *mode) { - assert(e); - assert(mode); - - if (e->vtable->pause) - e->vtable->pause(e, mode); -} - -/* - * Sessions - */ - -static int session_raise(idev_session *s, idev_event *ev) { - return s->event_fn(s, s->userdata, ev); -} - -static int session_raise_device_add(idev_session *s, idev_device *d) { - idev_event event = { - .type = IDEV_EVENT_DEVICE_ADD, - .device_add = { - .device = d, - }, - }; - - return session_raise(s, &event); -} - -static int session_raise_device_remove(idev_session *s, idev_device *d) { - idev_event event = { - .type = IDEV_EVENT_DEVICE_REMOVE, - .device_remove = { - .device = d, - }, - }; - - return session_raise(s, &event); -} - -int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) { - idev_event event = { - .type = IDEV_EVENT_DEVICE_DATA, - .device_data = { - .device = d, - .data = *data, - }, - }; - - return session_raise(s, &event); -} - -static int session_add_device(idev_session *s, idev_device *d) { - int r; - - assert(s); - assert(d); - - log_debug("idev: %s: add device '%s'", s->name, d->name); - - d->public = true; - r = session_raise_device_add(s, d); - if (r != 0) { - d->public = false; - goto error; - } - - return 0; - -error: - if (r < 0) - log_debug_errno(r, "idev: %s: error while adding device '%s': %m", - s->name, d->name); - return r; -} - -static int session_remove_device(idev_session *s, idev_device *d) { - int r, error = 0; - - assert(s); - assert(d); - - log_debug("idev: %s: remove device '%s'", s->name, d->name); - - d->public = false; - r = session_raise_device_remove(s, d); - if (r != 0) - error = r; - - idev_device_disable(d); - - if (error < 0) - log_debug_errno(error, "idev: %s: error while removing device '%s': %m", - s->name, d->name); - idev_device_free(d); - return error; -} - -static int session_add_element(idev_session *s, idev_element *e) { - assert(s); - assert(e); - - log_debug("idev: %s: add element '%s'", s->name, e->name); - - if (s->enabled) - element_enable(e); - - return 0; -} - -static int session_remove_element(idev_session *s, idev_element *e) { - int r, error = 0; - idev_device *d; - idev_link *l; - - assert(s); - assert(e); - - log_debug("idev: %s: remove element '%s'", s->name, e->name); - - while ((l = e->links)) { - d = l->device; - LIST_REMOVE(links_by_device, d->links, l); - LIST_REMOVE(links_by_element, e->links, l); - device_detach(d, l); - - if (!d->links) { - r = session_remove_device(s, d); - if (r != 0) - error = r; - } - - l->device = NULL; - l->element = NULL; - free(l); - } - - element_disable(e); - - if (error < 0) - log_debug_errno(r, "idev: %s: error while removing element '%s': %m", - s->name, e->name); - idev_element_free(e); - return error; -} - -idev_session *idev_find_session(idev_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->session_map, name); -} - -static int session_resume_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - idev_session *s = userdata; - idev_element *e; - uint32_t major, minor; - int r, fd; - - r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd); - if (r < 0) { - log_debug("idev: %s: erroneous ResumeDevice signal", s->name); - return 0; - } - - e = idev_find_evdev(s, makedev(major, minor)); - if (!e) - return 0; - - element_resume(e, fd); - return 0; -} - -static int session_pause_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - idev_session *s = userdata; - idev_element *e; - uint32_t major, minor; - const char *mode; - int r; - - r = sd_bus_message_read(signal, "uus", &major, &minor, &mode); - if (r < 0) { - log_debug("idev: %s: erroneous PauseDevice signal", s->name); - return 0; - } - - e = idev_find_evdev(s, makedev(major, minor)); - if (!e) - return 0; - - element_pause(e, mode); - return 0; -} - -static int session_setup_bus(idev_session *s) { - _cleanup_free_ char *match = NULL; - int r; - - if (!s->managed) - return 0; - - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='ResumeDevice'," - "path='", s->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(s->context->sysbus, - &s->slot_resume_device, - match, - session_resume_device_fn, - s); - if (r < 0) - return r; - - free(match); - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='PauseDevice'," - "path='", s->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(s->context->sysbus, - &s->slot_pause_device, - match, - session_pause_device_fn, - s); - if (r < 0) - return r; - - return 0; -} - -int idev_session_new(idev_session **out, - idev_context *c, - unsigned int flags, - const char *name, - idev_event_fn event_fn, - void *userdata) { - _cleanup_(idev_session_freep) idev_session *s = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(c, -EINVAL); - assert_return(name, -EINVAL); - assert_return(event_fn, -EINVAL); - assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL); - assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL); - assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL); - - s = new0(idev_session, 1); - if (!s) - return -ENOMEM; - - s->context = idev_context_ref(c); - s->custom = flags & IDEV_SESSION_CUSTOM; - s->managed = flags & IDEV_SESSION_MANAGED; - s->event_fn = event_fn; - s->userdata = userdata; - - s->name = strdup(name); - if (!s->name) - return -ENOMEM; - - if (s->managed) { - r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path); - if (r < 0) - return r; - } - - s->element_map = hashmap_new(&string_hash_ops); - if (!s->element_map) - return -ENOMEM; - - s->device_map = hashmap_new(&string_hash_ops); - if (!s->device_map) - return -ENOMEM; - - r = session_setup_bus(s); - if (r < 0) - return r; - - r = hashmap_put(c->session_map, s->name, s); - if (r < 0) - return r; - - *out = s; - s = NULL; - return 0; -} - -idev_session *idev_session_free(idev_session *s) { - idev_element *e; - - if (!s) - return NULL; - - while ((e = hashmap_first(s->element_map))) - session_remove_element(s, e); - - assert(hashmap_size(s->device_map) == 0); - - if (s->name) - hashmap_remove_value(s->context->session_map, s->name, s); - - s->slot_pause_device = sd_bus_slot_unref(s->slot_pause_device); - s->slot_resume_device = sd_bus_slot_unref(s->slot_resume_device); - s->context = idev_context_unref(s->context); - hashmap_free(s->device_map); - hashmap_free(s->element_map); - free(s->path); - free(s->name); - free(s); - - return NULL; -} - -bool idev_session_is_enabled(idev_session *s) { - return s && s->enabled; -} - -void idev_session_enable(idev_session *s) { - idev_element *e; - Iterator i; - - assert(s); - - if (!s->enabled) { - s->enabled = true; - HASHMAP_FOREACH(e, s->element_map, i) - element_enable(e); - } -} - -void idev_session_disable(idev_session *s) { - idev_element *e; - Iterator i; - - assert(s); - - if (s->enabled) { - s->enabled = false; - HASHMAP_FOREACH(e, s->element_map, i) - element_disable(e); - } -} - -static int add_link(idev_element *e, idev_device *d) { - idev_link *l; - - assert(e); - assert(d); - - l = new0(idev_link, 1); - if (!l) - return -ENOMEM; - - l->element = e; - l->device = d; - LIST_PREPEND(links_by_element, e->links, l); - LIST_PREPEND(links_by_device, d->links, l); - device_attach(d, l); - - return 0; -} - -static int guess_type(struct udev_device *d) { - const char *id_key; - - id_key = udev_device_get_property_value(d, "ID_INPUT_KEY"); - if (streq_ptr(id_key, "1")) - return IDEV_DEVICE_KEYBOARD; - - return IDEV_DEVICE_CNT; -} - -int idev_session_add_evdev(idev_session *s, struct udev_device *ud) { - idev_element *e; - idev_device *d; - dev_t devnum; - int r, type; - - assert_return(s, -EINVAL); - assert_return(ud, -EINVAL); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return 0; - - e = idev_find_evdev(s, devnum); - if (e) - return 0; - - r = idev_evdev_new(&e, s, ud); - if (r < 0) - return r; - - r = session_add_element(s, e); - if (r != 0) - return r; - - type = guess_type(ud); - if (type < 0) - return type; - - switch (type) { - case IDEV_DEVICE_KEYBOARD: - d = idev_find_keyboard(s, e->name); - if (d) { - log_debug("idev: %s: keyboard for new evdev element '%s' already available", - s->name, e->name); - return 0; - } - - r = idev_keyboard_new(&d, s, e->name); - if (r < 0) - return r; - - r = add_link(e, d); - if (r < 0) { - idev_device_free(d); - return r; - } - - return session_add_device(s, d); - default: - /* unknown elements are silently ignored */ - return 0; - } -} - -int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) { - idev_element *e; - dev_t devnum; - - assert(s); - assert(ud); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return 0; - - e = idev_find_evdev(s, devnum); - if (!e) - return 0; - - return session_remove_element(s, e); -} - -/* - * Contexts - */ - -int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) { - _cleanup_(idev_context_unrefp) idev_context *c = NULL; - - assert_return(out, -EINVAL); - assert_return(event, -EINVAL); - - c = new0(idev_context, 1); - if (!c) - return -ENOMEM; - - c->ref = 1; - c->event = sd_event_ref(event); - - if (sysbus) - c->sysbus = sd_bus_ref(sysbus); - - c->session_map = hashmap_new(&string_hash_ops); - if (!c->session_map) - return -ENOMEM; - - c->data_map = hashmap_new(&string_hash_ops); - if (!c->data_map) - return -ENOMEM; - - *out = c; - c = NULL; - return 0; -} - -static void context_cleanup(idev_context *c) { - assert(hashmap_size(c->data_map) == 0); - assert(hashmap_size(c->session_map) == 0); - - hashmap_free(c->data_map); - hashmap_free(c->session_map); - c->sysbus = sd_bus_unref(c->sysbus); - c->event = sd_event_unref(c->event); - free(c); -} - -idev_context *idev_context_ref(idev_context *c) { - assert_return(c, NULL); - assert_return(c->ref > 0, NULL); - - ++c->ref; - return c; -} - -idev_context *idev_context_unref(idev_context *c) { - if (!c) - return NULL; - - assert_return(c->ref > 0, NULL); - - if (--c->ref == 0) - context_cleanup(c); - - return NULL; -} diff --git a/src/libsystemd-terminal/idev.h b/src/libsystemd-terminal/idev.h deleted file mode 100644 index 241677cbbe..0000000000 --- a/src/libsystemd-terminal/idev.h +++ /dev/null @@ -1,221 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * IDev - */ - -#pragma once - -#include <libudev.h> -#include <linux/input.h> -#include <stdbool.h> -#include <xkbcommon/xkbcommon.h> -#include "sd-bus.h" -#include "sd-event.h" - -typedef struct idev_data idev_data; -typedef struct idev_data_evdev idev_data_evdev; -typedef struct idev_data_keyboard idev_data_keyboard; - -typedef struct idev_event idev_event; -typedef struct idev_device idev_device; -typedef struct idev_session idev_session; -typedef struct idev_context idev_context; - -/* - * Types - */ - -enum { - IDEV_ELEMENT_EVDEV, - IDEV_ELEMENT_CNT -}; - -enum { - IDEV_DEVICE_KEYBOARD, - IDEV_DEVICE_CNT -}; - -/* - * Evdev Elements - */ - -struct idev_data_evdev { - struct input_event event; -}; - -/* - * Keyboard Devices - */ - -struct xkb_state; - -enum { - IDEV_KBDMOD_IDX_SHIFT, - IDEV_KBDMOD_IDX_CTRL, - IDEV_KBDMOD_IDX_ALT, - IDEV_KBDMOD_IDX_LINUX, - IDEV_KBDMOD_IDX_CAPS, - IDEV_KBDMOD_CNT, - - IDEV_KBDMOD_SHIFT = 1 << IDEV_KBDMOD_IDX_SHIFT, - IDEV_KBDMOD_CTRL = 1 << IDEV_KBDMOD_IDX_CTRL, - IDEV_KBDMOD_ALT = 1 << IDEV_KBDMOD_IDX_ALT, - IDEV_KBDMOD_LINUX = 1 << IDEV_KBDMOD_IDX_LINUX, - IDEV_KBDMOD_CAPS = 1 << IDEV_KBDMOD_IDX_CAPS, -}; - -enum { - IDEV_KBDLED_IDX_NUM, - IDEV_KBDLED_IDX_CAPS, - IDEV_KBDLED_IDX_SCROLL, - IDEV_KBDLED_CNT, - - IDEV_KBDLED_NUM = 1 << IDEV_KBDLED_IDX_NUM, - IDEV_KBDLED_CAPS = 1 << IDEV_KBDLED_IDX_CAPS, - IDEV_KBDLED_SCROLL = 1 << IDEV_KBDLED_IDX_SCROLL, -}; - -struct idev_data_keyboard { - struct xkb_state *xkb_state; - int8_t ascii; - uint8_t value; - uint16_t keycode; - uint32_t mods; - uint32_t consumed_mods; - uint32_t n_syms; - uint32_t *keysyms; - uint32_t *codepoints; -}; - -static inline bool idev_kbdmatch(idev_data_keyboard *kdata, - uint32_t mods, uint32_t n_syms, - const uint32_t *syms) { - const uint32_t significant = IDEV_KBDMOD_SHIFT | - IDEV_KBDMOD_CTRL | - IDEV_KBDMOD_ALT | - IDEV_KBDMOD_LINUX; - uint32_t real; - - if (n_syms != kdata->n_syms) - return false; - - real = kdata->mods & ~kdata->consumed_mods & significant; - if (real != mods) - return false; - - return !memcmp(syms, kdata->keysyms, n_syms * sizeof(*syms)); -} - -#define IDEV_KBDMATCH(_kdata, _mods, _sym) \ - idev_kbdmatch((_kdata), (_mods), 1, (const uint32_t[]){ (_sym) }) - -/* - * Data Packets - */ - -enum { - IDEV_DATA_RESYNC, - IDEV_DATA_EVDEV, - IDEV_DATA_KEYBOARD, - IDEV_DATA_CNT -}; - -struct idev_data { - unsigned int type; - bool resync : 1; - - union { - idev_data_evdev evdev; - idev_data_keyboard keyboard; - }; -}; - -/* - * Events - */ - -enum { - IDEV_EVENT_DEVICE_ADD, - IDEV_EVENT_DEVICE_REMOVE, - IDEV_EVENT_DEVICE_DATA, - IDEV_EVENT_CNT -}; - -struct idev_event { - unsigned int type; - union { - struct { - idev_device *device; - } device_add, device_remove; - - struct { - idev_device *device; - idev_data data; - } device_data; - }; -}; - -typedef int (*idev_event_fn) (idev_session *s, void *userdata, idev_event *ev); - -/* - * Devices - */ - -void idev_device_enable(idev_device *d); -void idev_device_disable(idev_device *d); - -/* - * Sessions - */ - -enum { - IDEV_SESSION_CUSTOM = (1 << 0), - IDEV_SESSION_MANAGED = (1 << 1), -}; - -int idev_session_new(idev_session **out, - idev_context *c, - unsigned int flags, - const char *name, - idev_event_fn event_fn, - void *userdata); -idev_session *idev_session_free(idev_session *s); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_session*, idev_session_free); - -bool idev_session_is_enabled(idev_session *s); -void idev_session_enable(idev_session *s); -void idev_session_disable(idev_session *s); - -int idev_session_add_evdev(idev_session *s, struct udev_device *ud); -int idev_session_remove_evdev(idev_session *s, struct udev_device *ud); - -/* - * Contexts - */ - -int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus); -idev_context *idev_context_ref(idev_context *c); -idev_context *idev_context_unref(idev_context *c); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_context*, idev_context_unref); diff --git a/src/libsystemd-terminal/modeset.c b/src/libsystemd-terminal/modeset.c deleted file mode 100644 index 790a244772..0000000000 --- a/src/libsystemd-terminal/modeset.c +++ /dev/null @@ -1,482 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Modeset Testing - * The modeset tool attaches to the session of the caller and shows a - * test-pattern on all displays of this session. It is meant as debugging tool - * for the grdev infrastructure. - */ - -#include <drm_fourcc.h> -#include <errno.h> -#include <getopt.h> -#include <linux/kd.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <termios.h> -#include <unistd.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "build.h" -#include "macro.h" -#include "random-util.h" -#include "signal-util.h" -#include "util.h" -#include "grdev.h" -#include "sysview.h" - -typedef struct Modeset Modeset; - -struct Modeset { - char *session; - char *seat; - sd_event *event; - sd_bus *bus; - sd_event_source *exit_src; - sysview_context *sysview; - grdev_context *grdev; - grdev_session *grdev_session; - - uint8_t r, g, b; - bool r_up, g_up, b_up; - - bool my_tty : 1; - bool managed : 1; -}; - -static int modeset_exit_fn(sd_event_source *source, void *userdata) { - Modeset *m = userdata; - - if (m->grdev_session) - grdev_session_restore(m->grdev_session); - - return 0; -} - -static Modeset *modeset_free(Modeset *m) { - if (!m) - return NULL; - - m->grdev_session = grdev_session_free(m->grdev_session); - m->grdev = grdev_context_unref(m->grdev); - m->sysview = sysview_context_free(m->sysview); - m->exit_src = sd_event_source_unref(m->exit_src); - m->bus = sd_bus_unref(m->bus); - m->event = sd_event_unref(m->event); - free(m->seat); - free(m->session); - free(m); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free); - -static bool is_my_tty(const char *session) { - unsigned int vtnr; - struct stat st; - long mode; - int r; - - /* Using logind's Controller API is highly fragile if there is already - * a session controller running. If it is registered as controller - * itself, TakeControl will simply fail. But if its a legacy controller - * that does not use logind's controller API, we must never register - * our own controller. Otherwise, we really mess up the VT. Therefore, - * only run in managed mode if there's no-one else. Furthermore, never - * try to access graphics devices if there's someone else. Unlike input - * devices, graphics devies cannot be shared easily. */ - - if (!isatty(1)) - return false; - - if (!session) - return false; - - r = sd_session_get_vt(session, &vtnr); - if (r < 0 || vtnr < 1 || vtnr > 63) - return false; - - mode = 0; - r = ioctl(1, KDGETMODE, &mode); - if (r < 0 || mode != KD_TEXT) - return false; - - r = fstat(1, &st); - if (r < 0 || minor(st.st_rdev) != vtnr) - return false; - - return true; -} - -static int modeset_new(Modeset **out) { - _cleanup_(modeset_freep) Modeset *m = NULL; - int r; - - assert(out); - - m = new0(Modeset, 1); - if (!m) - return log_oom(); - - r = sd_pid_get_session(getpid(), &m->session); - if (r < 0) - return log_error_errno(r, "Cannot retrieve logind session: %m"); - - r = sd_session_get_seat(m->session, &m->seat); - if (r < 0) - return log_error_errno(r, "Cannot retrieve seat of logind session: %m"); - - m->my_tty = is_my_tty(m->session); - m->managed = m->my_tty && geteuid() > 0; - - m->r = rand() % 0xff; - m->g = rand() % 0xff; - m->b = rand() % 0xff; - m->r_up = m->g_up = m->b_up = true; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - r = sd_bus_open_system(&m->bus); - if (r < 0) - return r; - - r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return r; - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m); - if (r < 0) - return r; - - /* schedule before sd-bus close */ - r = sd_event_source_set_priority(m->exit_src, -10); - if (r < 0) - return r; - - r = sysview_context_new(&m->sysview, - SYSVIEW_CONTEXT_SCAN_LOGIND | - SYSVIEW_CONTEXT_SCAN_DRM, - m->event, - m->bus, - NULL); - if (r < 0) - return r; - - r = grdev_context_new(&m->grdev, m->event, m->bus); - if (r < 0) - return r; - - *out = m; - m = NULL; - return 0; -} - -static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) { - uint8_t next; - - /* generate smoothly morphing colors */ - - next = cur + (*up ? 1 : -1) * (rand() % mod); - if ((*up && next < cur) || (!*up && next > cur)) { - *up = !*up; - next = cur; - } - - return next; -} - -static void modeset_draw(Modeset *m, const grdev_display_target *t) { - uint32_t j, k, *b; - uint8_t *l; - - assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888); - assert(!t->rotate); - assert(!t->flip); - - l = t->back->maps[0]; - for (j = 0; j < t->height; ++j) { - for (k = 0; k < t->width; ++k) { - b = (uint32_t*)l; - b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b; - } - - l += t->back->strides[0]; - } -} - -static void modeset_render(Modeset *m, grdev_display *d) { - const grdev_display_target *t; - - m->r = next_color(&m->r_up, m->r, 4); - m->g = next_color(&m->g_up, m->g, 3); - m->b = next_color(&m->b_up, m->b, 2); - - GRDEV_DISPLAY_FOREACH_TARGET(d, t) { - modeset_draw(m, t); - grdev_display_flip_target(d, t); - } - - grdev_session_commit(m->grdev_session); -} - -static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) { - Modeset *m = userdata; - - switch (ev->type) { - case GRDEV_EVENT_DISPLAY_ADD: - grdev_display_enable(ev->display_add.display); - break; - case GRDEV_EVENT_DISPLAY_REMOVE: - break; - case GRDEV_EVENT_DISPLAY_CHANGE: - break; - case GRDEV_EVENT_DISPLAY_FRAME: - modeset_render(m, ev->display_frame.display); - break; - } -} - -static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { - unsigned int flags, type; - Modeset *m = userdata; - sysview_device *d; - const char *name; - int r; - - switch (ev->type) { - case SYSVIEW_EVENT_SESSION_FILTER: - if (streq_ptr(m->session, ev->session_filter.id)) - return 1; - - break; - case SYSVIEW_EVENT_SESSION_ADD: - assert(!m->grdev_session); - - name = sysview_session_get_name(ev->session_add.session); - flags = 0; - - if (m->managed) - flags |= GRDEV_SESSION_MANAGED; - - r = grdev_session_new(&m->grdev_session, - m->grdev, - flags, - name, - modeset_grdev_fn, - m); - if (r < 0) - return log_error_errno(r, "Cannot create grdev session: %m"); - - if (m->managed) { - r = sysview_session_take_control(ev->session_add.session); - if (r < 0) - return log_error_errno(r, "Cannot request session control: %m"); - } - - grdev_session_enable(m->grdev_session); - - break; - case SYSVIEW_EVENT_SESSION_REMOVE: - if (!m->grdev_session) - return 0; - - grdev_session_restore(m->grdev_session); - grdev_session_disable(m->grdev_session); - m->grdev_session = grdev_session_free(m->grdev_session); - if (sd_event_get_exit_code(m->event, &r) == -ENODATA) - sd_event_exit(m->event, 0); - break; - case SYSVIEW_EVENT_SESSION_ATTACH: - d = ev->session_attach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_DRM) - grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d)); - - break; - case SYSVIEW_EVENT_SESSION_DETACH: - d = ev->session_detach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_DRM) - grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d)); - - break; - case SYSVIEW_EVENT_SESSION_REFRESH: - d = ev->session_refresh.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_DRM) - grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud); - - break; - case SYSVIEW_EVENT_SESSION_CONTROL: - r = ev->session_control.error; - if (r < 0) - return log_error_errno(r, "Cannot acquire session control: %m"); - - r = ioctl(1, KDSKBMODE, K_UNICODE); - if (r < 0) - return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m"); - - break; - } - - return 0; -} - -static int modeset_run(Modeset *m) { - struct termios in_attr, saved_attr; - int r; - - assert(m); - - if (!m->my_tty) { - log_warning("You need to run this program on a free VT"); - return -EACCES; - } - - if (!m->managed && geteuid() > 0) - log_warning("You run in unmanaged mode without being root. This is likely to fail.."); - - printf("modeset - Show test pattern on selected graphics devices\n" - " Running on seat '%s' in user-session '%s'\n" - " Exit by pressing ^C\n\n", - m->seat ? : "seat0", m->session ? : "<none>"); - - r = sysview_context_start(m->sysview, modeset_sysview_fn, m); - if (r < 0) - goto out; - - r = tcgetattr(0, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - saved_attr = in_attr; - in_attr.c_lflag &= ~ECHO; - - r = tcsetattr(0, TCSANOW, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - r = sd_event_loop(m->event); - tcsetattr(0, TCSANOW, &saved_attr); - printf("exiting..\n"); - -out: - sysview_context_stop(m->sysview); - return r; -} - -static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Show test pattern on all selected graphics devices.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {}, - }; - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - case 'h': - help(); - return 0; - - case ARG_VERSION: - puts(PACKAGE_STRING); - puts(SYSTEMD_FEATURES); - return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind) { - log_error("Too many arguments"); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_(modeset_freep) Modeset *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - initialize_srand(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = modeset_new(&m); - if (r < 0) - goto finish; - - r = modeset_run(m); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/libsystemd-terminal/subterm.c b/src/libsystemd-terminal/subterm.c deleted file mode 100644 index 5f12540111..0000000000 --- a/src/libsystemd-terminal/subterm.c +++ /dev/null @@ -1,981 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Stacked Terminal-Emulator - * This is an interactive test of the term_screen implementation. It runs a - * fully-fletched terminal-emulator inside of a parent TTY. That is, instead of - * rendering the terminal as X11-window, it renders it as sub-window in the - * parent TTY. Think of this like what "GNU-screen" does. - */ - -#include <errno.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/ioctl.h> -#include <termios.h> -#include "sd-event.h" -#include "macro.h" -#include "pty.h" -#include "ring.h" -#include "signal-util.h" -#include "utf8.h" -#include "util.h" -#include "term-internal.h" - -typedef struct Output Output; -typedef struct Terminal Terminal; - -struct Output { - int fd; - unsigned int width; - unsigned int height; - unsigned int in_width; - unsigned int in_height; - unsigned int cursor_x; - unsigned int cursor_y; - - char obuf[4096]; - size_t n_obuf; - - bool resized : 1; - bool in_menu : 1; -}; - -struct Terminal { - sd_event *event; - sd_event_source *frame_timer; - Output *output; - term_utf8 utf8; - term_parser *parser; - term_screen *screen; - - int in_fd; - int out_fd; - struct termios saved_in_attr; - struct termios saved_out_attr; - - Pty *pty; - Ring out_ring; - - bool is_scheduled : 1; - bool is_dirty : 1; - bool is_menu : 1; -}; - -/* - * Output Handling - */ - -#define BORDER_HORIZ "\xe2\x94\x81" -#define BORDER_VERT "\xe2\x94\x83" -#define BORDER_VERT_RIGHT "\xe2\x94\xa3" -#define BORDER_VERT_LEFT "\xe2\x94\xab" -#define BORDER_DOWN_RIGHT "\xe2\x94\x8f" -#define BORDER_DOWN_LEFT "\xe2\x94\x93" -#define BORDER_UP_RIGHT "\xe2\x94\x97" -#define BORDER_UP_LEFT "\xe2\x94\x9b" - -static int output_winch(Output *o) { - struct winsize wsz = { }; - int r; - - assert_return(o, -EINVAL); - - r = ioctl(o->fd, TIOCGWINSZ, &wsz); - if (r < 0) - return log_error_errno(errno, "error: cannot read window-size: %m"); - - if (wsz.ws_col != o->width || wsz.ws_row != o->height) { - o->width = wsz.ws_col; - o->height = wsz.ws_row; - o->in_width = MAX(o->width, 2U) - 2; - o->in_height = MAX(o->height, 6U) - 6; - o->resized = true; - } - - return 0; -} - -static int output_flush(Output *o) { - int r; - - if (o->n_obuf < 1) - return 0; - - r = loop_write(o->fd, o->obuf, o->n_obuf, false); - if (r < 0) - return log_error_errno(r, "error: cannot write to TTY: %m"); - - o->n_obuf = 0; - - return 0; -} - -static int output_write(Output *o, const void *buf, size_t size) { - ssize_t len; - int r; - - assert_return(o, -EINVAL); - assert_return(buf || size < 1, -EINVAL); - - if (size < 1) - return 0; - - if (o->n_obuf + size > o->n_obuf && o->n_obuf + size <= sizeof(o->obuf)) { - memcpy(o->obuf + o->n_obuf, buf, size); - o->n_obuf += size; - return 0; - } - - r = output_flush(o); - if (r < 0) - return r; - - len = loop_write(o->fd, buf, size, false); - if (len < 0) - return log_error_errno(len, "error: cannot write to TTY (%zd): %m", len); - - return 0; -} - -_printf_(3,0) -static int output_vnprintf(Output *o, size_t max, const char *format, va_list args) { - char buf[max]; - int r; - - assert_return(o, -EINVAL); - assert_return(format, -EINVAL); - assert_return(max <= 4096, -EINVAL); - - r = MIN(vsnprintf(buf, max, format, args), (int) max); - - return output_write(o, buf, r); -} - -_printf_(3,4) -static int output_nprintf(Output *o, size_t max, const char *format, ...) { - va_list args; - int r; - - va_start(args, format); - r = output_vnprintf(o, max, format, args); - va_end(args); - - return r; -} - -_printf_(2,0) -static int output_vprintf(Output *o, const char *format, va_list args) { - char buf[4096]; - int r; - - assert_return(o, -EINVAL); - assert_return(format, -EINVAL); - - r = vsnprintf(buf, sizeof(buf), format, args); - - assert_return(r < (ssize_t)sizeof(buf), -ENOBUFS); - - return output_write(o, buf, r); -} - -_printf_(2,3) -static int output_printf(Output *o, const char *format, ...) { - va_list args; - int r; - - va_start(args, format); - r = output_vprintf(o, format, args); - va_end(args); - - return r; -} - -static int output_move_to(Output *o, unsigned int x, unsigned int y) { - int r; - - assert_return(o, -EINVAL); - - /* force the \e[H code as o->cursor_x/y might be out-of-date */ - - r = output_printf(o, "\e[%u;%uH", y + 1, x + 1); - if (r < 0) - return r; - - o->cursor_x = x; - o->cursor_y = y; - return 0; -} - -static int output_print_line(Output *o, size_t len) { - const char line[] = - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ; - const size_t max = (sizeof(line) - 1) / (sizeof(BORDER_HORIZ) - 1); - size_t i; - int r = 0; - - assert_return(o, -EINVAL); - - for ( ; len > 0; len -= i) { - i = (len > max) ? max : len; - r = output_write(o, line, i * (sizeof(BORDER_HORIZ) - 1)); - if (r < 0) - break; - } - - return r; -} - -_printf_(2,3) -static int output_frame_printl(Output *o, const char *format, ...) { - va_list args; - int r; - - assert(o); - assert(format); - - /* out of frame? */ - if (o->cursor_y < 3 || o->cursor_y >= o->height - 3) - return 0; - - va_start(args, format); - r = output_vnprintf(o, o->width - 2, format, args); - va_end(args); - - if (r < 0) - return r; - - return output_move_to(o, 1, o->cursor_y + 1); -} - -static Output *output_free(Output *o) { - if (!o) - return NULL; - - /* re-enable cursor */ - output_printf(o, "\e[?25h"); - /* disable alternate screen buffer */ - output_printf(o, "\e[?1049l"); - output_flush(o); - - /* o->fd is owned by the caller */ - free(o); - - return NULL; -} - -static int output_new(Output **out, int fd) { - Output *o; - int r; - - assert_return(out, -EINVAL); - - o = new0(Output, 1); - if (!o) - return log_oom(); - - o->fd = fd; - - r = output_winch(o); - if (r < 0) - goto error; - - /* enable alternate screen buffer */ - r = output_printf(o, "\e[?1049h"); - if (r < 0) - goto error; - - /* always hide cursor */ - r = output_printf(o, "\e[?25l"); - if (r < 0) - goto error; - - r = output_flush(o); - if (r < 0) - goto error; - - *out = o; - return 0; - -error: - output_free(o); - return r; -} - -static void output_draw_frame(Output *o) { - unsigned int i; - - assert(o); - - /* print header-frame */ - - output_printf(o, BORDER_DOWN_RIGHT); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_DOWN_LEFT - "\r\n" - BORDER_VERT - "\e[2;%uH" /* cursor-position: 2/x */ - BORDER_VERT - "\r\n" - BORDER_VERT_RIGHT, - o->width); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_VERT_LEFT - "\r\n"); - - /* print body-frame */ - - for (i = 0; i < o->in_height; ++i) { - output_printf(o, BORDER_VERT - "\e[%u;%uH" /* cursor-position: 2/x */ - BORDER_VERT - "\r\n", - i + 4, o->width); - } - - /* print footer-frame */ - - output_printf(o, BORDER_VERT_RIGHT); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_VERT_LEFT - "\r\n" - BORDER_VERT - "\e[%u;%uH" /* cursor-position: 2/x */ - BORDER_VERT - "\r\n" - BORDER_UP_RIGHT, - o->height - 1, o->width); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_UP_LEFT); - - /* print header/footer text */ - - output_printf(o, "\e[2;3H"); - output_nprintf(o, o->width - 4, "systemd - embedded terminal emulator"); - output_printf(o, "\e[%u;3H", o->height - 1); - output_nprintf(o, o->width - 4, "press ^C to enter menu"); -} - -static void output_draw_menu(Output *o) { - assert(o); - - output_frame_printl(o, "%s", ""); - output_frame_printl(o, " Menu: (the following keys are recognized)"); - output_frame_printl(o, " q: quit"); - output_frame_printl(o, " ^C: send ^C to the PTY"); -} - -static int output_draw_cell_fn(term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width) { - Output *o = userdata; - size_t k, ulen; - char utf8[4]; - - if (x >= o->in_width || y >= o->in_height) - return 0; - - if (x == 0 && y != 0) - output_printf(o, "\e[m\r\n" BORDER_VERT); - - switch (attr->fg.ccode) { - case TERM_CCODE_DEFAULT: - output_printf(o, "\e[39m"); - break; - case TERM_CCODE_256: - output_printf(o, "\e[38;5;%um", attr->fg.c256); - break; - case TERM_CCODE_RGB: - output_printf(o, "\e[38;2;%u;%u;%um", attr->fg.red, attr->fg.green, attr->fg.blue); - break; - case TERM_CCODE_BLACK ... TERM_CCODE_WHITE: - output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 30); - break; - case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE: - output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_LIGHT_BLACK + 90); - break; - } - - switch (attr->bg.ccode) { - case TERM_CCODE_DEFAULT: - output_printf(o, "\e[49m"); - break; - case TERM_CCODE_256: - output_printf(o, "\e[48;5;%um", attr->bg.c256); - break; - case TERM_CCODE_RGB: - output_printf(o, "\e[48;2;%u;%u;%um", attr->bg.red, attr->bg.green, attr->bg.blue); - break; - case TERM_CCODE_BLACK ... TERM_CCODE_WHITE: - output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_BLACK + 40); - break; - case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE: - output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_LIGHT_BLACK + 100); - break; - } - - output_printf(o, "\e[%u;%u;%u;%u;%u;%um", - attr->bold ? 1 : 22, - attr->italic ? 3 : 23, - attr->underline ? 4 : 24, - attr->inverse ? 7 : 27, - attr->blink ? 5 : 25, - attr->hidden ? 8 : 28); - - if (n_ch < 1) { - output_printf(o, " "); - } else { - for (k = 0; k < n_ch; ++k) { - ulen = utf8_encode_unichar(utf8, ch[k]); - output_write(o, utf8, ulen); - } - } - - return 0; -} - -static void output_draw_screen(Output *o, term_screen *s) { - assert(o); - assert(s); - - term_screen_draw(s, output_draw_cell_fn, o, NULL); - - output_printf(o, "\e[m"); -} - -static void output_draw(Output *o, bool menu, term_screen *screen) { - assert(o); - - /* - * This renders the contenst of the terminal. The layout contains a - * header, the main body and a footer. Around all areas we draw a - * border. It looks something like this: - * - * +----------------------------------------------------+ - * | Header | - * +----------------------------------------------------+ - * | | - * | | - * | | - * | Body | - * | | - * | | - * ~ ~ - * ~ ~ - * +----------------------------------------------------+ - * | Footer | - * +----------------------------------------------------+ - * - * The body is the part that grows vertically. - * - * We need at least 6 vertical lines to render the screen. This would - * leave 0 lines for the body. Therefore, we require 7 lines so there's - * at least one body line. Similarly, we need 2 horizontal cells for the - * frame, so we require 3. - * If the window is too small, we print an error message instead. - */ - - if (o->in_width < 1 || o->in_height < 1) { - output_printf(o, "\e[2J" /* erase-in-display: whole screen */ - "\e[H"); /* cursor-position: home */ - output_printf(o, "error: screen too small, need at least 3x7 cells"); - output_flush(o); - return; - } - - /* hide cursor */ - output_printf(o, "\e[?25l"); - - /* frame-content is contant; only resizes can change it */ - if (o->resized || o->in_menu != menu) { - output_printf(o, "\e[2J" /* erase-in-display: whole screen */ - "\e[H"); /* cursor-position: home */ - output_draw_frame(o); - o->resized = false; - o->in_menu = menu; - } - - /* move cursor to child's position */ - output_move_to(o, 1, 3); - - if (menu) - output_draw_menu(o); - else - output_draw_screen(o, screen); - - /* - * Hack: sd-term was not written to support TTY as output-objects, thus - * expects callers to use term_screen_feed_keyboard(). However, we - * forward TTY input directly. Hence, we're not notified about keypad - * changes. Update the related modes djring redraw to keep them at least - * in sync. - */ - if (screen->flags & TERM_FLAG_CURSOR_KEYS) - output_printf(o, "\e[?1h"); - else - output_printf(o, "\e[?1l"); - - if (screen->flags & TERM_FLAG_KEYPAD_MODE) - output_printf(o, "\e="); - else - output_printf(o, "\e>"); - - output_flush(o); -} - -/* - * Terminal Handling - */ - -static void terminal_dirty(Terminal *t) { - usec_t usec; - int r; - - assert(t); - - if (t->is_scheduled) { - t->is_dirty = true; - return; - } - - /* 16ms timer */ - r = sd_event_now(t->event, CLOCK_MONOTONIC, &usec); - assert(r >= 0); - - usec += 16 * USEC_PER_MSEC; - r = sd_event_source_set_time(t->frame_timer, usec); - if (r >= 0) { - r = sd_event_source_set_enabled(t->frame_timer, SD_EVENT_ONESHOT); - if (r >= 0) - t->is_scheduled = true; - } - - t->is_dirty = false; - output_draw(t->output, t->is_menu, t->screen); -} - -static int terminal_frame_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) { - Terminal *t = userdata; - - t->is_scheduled = false; - if (t->is_dirty) - terminal_dirty(t); - - return 0; -} - -static int terminal_winch_fn(sd_event_source *source, const struct signalfd_siginfo *ssi, void *userdata) { - Terminal *t = userdata; - int r; - - output_winch(t->output); - - if (t->pty) { - r = pty_resize(t->pty, t->output->in_width, t->output->in_height); - if (r < 0) - log_error_errno(r, "error: pty_resize() (%d): %m", r); - } - - r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height); - if (r < 0) - log_error_errno(r, "error: term_screen_resize() (%d): %m", r); - - terminal_dirty(t); - - return 0; -} - -static int terminal_push_tmp(Terminal *t, uint32_t ucs4) { - char buf[4]; - size_t len; - int r; - - assert(t); - - len = utf8_encode_unichar(buf, ucs4); - if (len < 1) - return 0; - - r = ring_push(&t->out_ring, buf, len); - if (r < 0) - log_oom(); - - return r; -} - -static int terminal_write_tmp(Terminal *t) { - struct iovec vec[2]; - size_t num, i; - int r; - - assert(t); - - num = ring_peek(&t->out_ring, vec); - if (num < 1) - return 0; - - if (t->pty) { - for (i = 0; i < num; ++i) { - r = pty_write(t->pty, vec[i].iov_base, vec[i].iov_len); - if (r < 0) - return log_error_errno(r, "error: cannot write to PTY (%d): %m", r); - } - } - - ring_flush(&t->out_ring); - return 0; -} - -static void terminal_discard_tmp(Terminal *t) { - assert(t); - - ring_flush(&t->out_ring); -} - -static int terminal_menu(Terminal *t, const term_seq *seq) { - switch (seq->type) { - case TERM_SEQ_IGNORE: - break; - case TERM_SEQ_GRAPHIC: - switch (seq->terminator) { - case 'q': - sd_event_exit(t->event, 0); - return 0; - } - - break; - case TERM_SEQ_CONTROL: - switch (seq->terminator) { - case 0x03: - terminal_push_tmp(t, 0x03); - terminal_write_tmp(t); - break; - } - - break; - } - - t->is_menu = false; - terminal_dirty(t); - - return 0; -} - -static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Terminal *t = userdata; - char buf[4096]; - ssize_t len, i; - int r, type; - - len = read(fd, buf, sizeof(buf)); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - log_error_errno(errno, "error: cannot read from TTY (%d): %m", -errno); - return -errno; - } - - for (i = 0; i < len; ++i) { - const term_seq *seq; - uint32_t *str; - size_t n_str, j; - - n_str = term_utf8_decode(&t->utf8, &str, buf[i]); - for (j = 0; j < n_str; ++j) { - type = term_parser_feed(t->parser, &seq, str[j]); - if (type < 0) - return log_error_errno(type, "error: term_parser_feed() (%d): %m", type); - - if (!t->is_menu) { - r = terminal_push_tmp(t, str[j]); - if (r < 0) - return r; - } - - if (type == TERM_SEQ_NONE) { - /* We only intercept one-char sequences, so in - * case term_parser_feed() couldn't parse a - * sequence, it is waiting for more data. We - * know it can never be a one-char sequence - * then, so we can safely forward the data. - * This avoids withholding ESC or other values - * that may be one-shot depending on the - * application. */ - r = terminal_write_tmp(t); - if (r < 0) - return r; - } else if (t->is_menu) { - r = terminal_menu(t, seq); - if (r < 0) - return r; - } else if (seq->type == TERM_SEQ_CONTROL && seq->terminator == 0x03) { /* ^C opens the menu */ - terminal_discard_tmp(t); - t->is_menu = true; - terminal_dirty(t); - } else { - r = terminal_write_tmp(t); - if (r < 0) - return r; - } - } - } - - return 0; -} - -static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) { - Terminal *t = userdata; - int r; - - switch (event) { - case PTY_CHILD: - sd_event_exit(t->event, 0); - break; - case PTY_DATA: - r = term_screen_feed_text(t->screen, ptr, size); - if (r < 0) - return log_error_errno(r, "error: term_screen_feed_text() (%d): %m", r); - - terminal_dirty(t); - break; - } - - return 0; -} - -static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) { - Terminal *t = userdata; - int r; - - if (!t->pty) - return 0; - - r = ring_push(&t->out_ring, buf, size); - if (r < 0) - log_oom(); - - return r; -} - -static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) { - return 0; -} - -static Terminal *terminal_free(Terminal *t) { - if (!t) - return NULL; - - ring_clear(&t->out_ring); - term_screen_unref(t->screen); - term_parser_free(t->parser); - output_free(t->output); - sd_event_source_unref(t->frame_timer); - sd_event_unref(t->event); - tcsetattr(t->in_fd, TCSANOW, &t->saved_in_attr); - tcsetattr(t->out_fd, TCSANOW, &t->saved_out_attr); - free(t); - - return NULL; -} - -static int terminal_new(Terminal **out, int in_fd, int out_fd) { - struct termios in_attr, out_attr; - Terminal *t; - int r; - - assert_return(out, -EINVAL); - - r = tcgetattr(in_fd, &in_attr); - if (r < 0) - return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno); - - r = tcgetattr(out_fd, &out_attr); - if (r < 0) - return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno); - - t = new0(Terminal, 1); - if (!t) - return log_oom(); - - t->in_fd = in_fd; - t->out_fd = out_fd; - memcpy(&t->saved_in_attr, &in_attr, sizeof(in_attr)); - memcpy(&t->saved_out_attr, &out_attr, sizeof(out_attr)); - - cfmakeraw(&in_attr); - cfmakeraw(&out_attr); - - r = tcsetattr(t->in_fd, TCSANOW, &in_attr); - if (r < 0) { - log_error_errno(r, "error: tcsetattr() (%d): %m", r); - goto error; - } - - r = tcsetattr(t->out_fd, TCSANOW, &out_attr); - if (r < 0) { - log_error_errno(r, "error: tcsetattr() (%d): %m", r); - goto error; - } - - r = sd_event_default(&t->event); - if (r < 0) { - log_error_errno(r, "error: sd_event_default() (%d): %m", r); - goto error; - } - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1); - if (r < 0) { - log_error_errno(r, "error: sigprocmask_many() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - /* force initial redraw on event-loop enter */ - t->is_dirty = true; - r = sd_event_add_time(t->event, &t->frame_timer, CLOCK_MONOTONIC, 0, 0, terminal_frame_timer_fn, t); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_time() (%d): %m", r); - goto error; - } - - r = output_new(&t->output, out_fd); - if (r < 0) - goto error; - - r = term_parser_new(&t->parser, true); - if (r < 0) - goto error; - - r = term_screen_new(&t->screen, terminal_write_fn, t, terminal_cmd_fn, t); - if (r < 0) - goto error; - - r = term_screen_set_answerback(t->screen, "systemd-subterm"); - if (r < 0) - goto error; - - r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height); - if (r < 0) { - log_error_errno(r, "error: term_screen_resize() (%d): %m", r); - goto error; - } - - r = sd_event_add_io(t->event, NULL, in_fd, EPOLLIN, terminal_io_fn, t); - if (r < 0) - goto error; - - *out = t; - return 0; - -error: - terminal_free(t); - return r; -} - -static int terminal_run(Terminal *t) { - pid_t pid; - - assert_return(t, -EINVAL); - - pid = pty_fork(&t->pty, t->event, terminal_pty_fn, t, t->output->in_width, t->output->in_height); - if (pid < 0) - return log_error_errno(pid, "error: cannot fork PTY (%d): %m", pid); - else if (pid == 0) { - /* child */ - - char **argv = (char*[]){ - (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL, - NULL - }; - - setenv("TERM", "xterm-256color", 1); - setenv("COLORTERM", "systemd-subterm", 1); - - execve(argv[0], argv, environ); - log_error_errno(errno, "error: cannot exec %s (%d): %m", argv[0], -errno); - _exit(1); - } - - /* parent */ - - return sd_event_loop(t->event); -} - -/* - * Context Handling - */ - -int main(int argc, char *argv[]) { - Terminal *t = NULL; - int r; - - r = terminal_new(&t, 0, 1); - if (r < 0) - goto out; - - r = terminal_run(t); - if (r < 0) - goto out; - -out: - if (r < 0) - log_error_errno(r, "error: terminal failed (%d): %m", r); - terminal_free(t); - return -r; -} diff --git a/src/libsystemd-terminal/sysview-internal.h b/src/libsystemd-terminal/sysview-internal.h deleted file mode 100644 index 251c8d7300..0000000000 --- a/src/libsystemd-terminal/sysview-internal.h +++ /dev/null @@ -1,144 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <inttypes.h> -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "list.h" -#include "macro.h" -#include "util.h" -#include "sysview.h" - -/* - * Devices - */ - -struct sysview_device { - sysview_seat *seat; - char *name; - unsigned int type; - - union { - struct { - struct udev_device *ud; - } evdev, drm; - }; -}; - -sysview_device *sysview_find_device(sysview_context *c, const char *name); - -int sysview_device_new(sysview_device **out, sysview_seat *seat, const char *name); -sysview_device *sysview_device_free(sysview_device *device); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_device*, sysview_device_free); - -/* - * Sessions - */ - -struct sysview_session { - sysview_seat *seat; - char *name; - char *path; - void *userdata; - - sd_bus_slot *slot_take_control; - - bool custom : 1; - bool public : 1; - bool wants_control : 1; - bool has_control : 1; -}; - -sysview_session *sysview_find_session(sysview_context *c, const char *name); - -int sysview_session_new(sysview_session **out, sysview_seat *seat, const char *name); -sysview_session *sysview_session_free(sysview_session *session); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_session*, sysview_session_free); - -/* - * Seats - */ - -struct sysview_seat { - sysview_context *context; - char *name; - char *path; - - Hashmap *session_map; - Hashmap *device_map; - - bool scanned : 1; - bool public : 1; -}; - -sysview_seat *sysview_find_seat(sysview_context *c, const char *name); - -int sysview_seat_new(sysview_seat **out, sysview_context *c, const char *name); -sysview_seat *sysview_seat_free(sysview_seat *seat); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_seat*, sysview_seat_free); - -/* - * Contexts - */ - -struct sysview_context { - sd_event *event; - sd_bus *sysbus; - struct udev *ud; - uint64_t custom_sid; - unsigned int n_probe; - - Hashmap *seat_map; - Hashmap *session_map; - Hashmap *device_map; - - sd_event_source *scan_src; - sysview_event_fn event_fn; - void *userdata; - - /* udev scanner */ - struct udev_monitor *ud_monitor; - sd_event_source *ud_monitor_src; - - /* logind scanner */ - sd_bus_slot *ld_slot_manager_signal; - sd_bus_slot *ld_slot_list_seats; - sd_bus_slot *ld_slot_list_sessions; - - bool scan_logind : 1; - bool scan_evdev : 1; - bool scan_drm : 1; - bool running : 1; - bool scanned : 1; - bool rescan : 1; - bool settled : 1; -}; - -int sysview_context_rescan(sysview_context *c); diff --git a/src/libsystemd-terminal/sysview.c b/src/libsystemd-terminal/sysview.c deleted file mode 100644 index 2e9b15859a..0000000000 --- a/src/libsystemd-terminal/sysview.c +++ /dev/null @@ -1,1554 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <inttypes.h> -#include <libudev.h> -#include <stdbool.h> -#include <stdlib.h> -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "macro.h" -#include "udev-util.h" -#include "util.h" -#include "bus-util.h" -#include "sysview.h" -#include "sysview-internal.h" - -static int context_raise_session_control(sysview_context *c, sysview_session *session, int error); - -/* - * Devices - */ - -sysview_device *sysview_find_device(sysview_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->device_map, name); -} - -int sysview_device_new(sysview_device **out, sysview_seat *seat, const char *name) { - _cleanup_(sysview_device_freep) sysview_device *device = NULL; - int r; - - assert_return(seat, -EINVAL); - assert_return(name, -EINVAL); - - device = new0(sysview_device, 1); - if (!device) - return -ENOMEM; - - device->seat = seat; - device->type = (unsigned)-1; - - device->name = strdup(name); - if (!device->name) - return -ENOMEM; - - r = hashmap_put(seat->context->device_map, device->name, device); - if (r < 0) - return r; - - r = hashmap_put(seat->device_map, device->name, device); - if (r < 0) - return r; - - if (out) - *out = device; - device = NULL; - return 0; -} - -sysview_device *sysview_device_free(sysview_device *device) { - if (!device) - return NULL; - - if (device->name) { - hashmap_remove_value(device->seat->device_map, device->name, device); - hashmap_remove_value(device->seat->context->device_map, device->name, device); - } - - switch (device->type) { - case SYSVIEW_DEVICE_EVDEV: - device->evdev.ud = udev_device_unref(device->evdev.ud); - break; - case SYSVIEW_DEVICE_DRM: - device->drm.ud = udev_device_unref(device->drm.ud); - break; - } - - free(device->name); - free(device); - - return NULL; -} - -const char *sysview_device_get_name(sysview_device *device) { - assert_return(device, NULL); - - return device->name; -} - -unsigned int sysview_device_get_type(sysview_device *device) { - assert_return(device, (unsigned)-1); - - return device->type; -} - -struct udev_device *sysview_device_get_ud(sysview_device *device) { - assert_return(device, NULL); - - switch (device->type) { - case SYSVIEW_DEVICE_EVDEV: - return device->evdev.ud; - case SYSVIEW_DEVICE_DRM: - return device->drm.ud; - default: - assert_return(0, NULL); - } -} - -static int device_new_ud(sysview_device **out, sysview_seat *seat, unsigned int type, struct udev_device *ud) { - _cleanup_(sysview_device_freep) sysview_device *device = NULL; - int r; - - assert_return(seat, -EINVAL); - assert_return(ud, -EINVAL); - - r = sysview_device_new(&device, seat, udev_device_get_syspath(ud)); - if (r < 0) - return r; - - device->type = type; - - switch (type) { - case SYSVIEW_DEVICE_EVDEV: - device->evdev.ud = udev_device_ref(ud); - break; - case SYSVIEW_DEVICE_DRM: - device->drm.ud = udev_device_ref(ud); - break; - default: - assert_not_reached("sysview: invalid udev-device type"); - } - - if (out) - *out = device; - device = NULL; - return 0; -} - -/* - * Sessions - */ - -sysview_session *sysview_find_session(sysview_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->session_map, name); -} - -int sysview_session_new(sysview_session **out, sysview_seat *seat, const char *name) { - _cleanup_(sysview_session_freep) sysview_session *session = NULL; - int r; - - assert_return(seat, -EINVAL); - - session = new0(sysview_session, 1); - if (!session) - return -ENOMEM; - - session->seat = seat; - - if (name) { - /* - * If a name is given, we require it to be a logind session - * name. The session will be put in managed mode and we use - * logind to request controller access. - */ - - session->name = strdup(name); - if (!session->name) - return -ENOMEM; - - r = sd_bus_path_encode("/org/freedesktop/login1/session", - session->name, &session->path); - if (r < 0) - return r; - - session->custom = false; - } else { - /* - * No session name was given. We assume this is an unmanaged - * session controlled by the application. We don't use logind - * at all and leave session management to the application. The - * name of the session-object is set to a unique random string - * that does not clash with the logind namespace. - */ - - r = asprintf(&session->name, "@custom%" PRIu64, - ++seat->context->custom_sid); - if (r < 0) - return -ENOMEM; - - session->custom = true; - } - - r = hashmap_put(seat->context->session_map, session->name, session); - if (r < 0) - return r; - - r = hashmap_put(seat->session_map, session->name, session); - if (r < 0) - return r; - - if (out) - *out = session; - session = NULL; - return 0; -} - -sysview_session *sysview_session_free(sysview_session *session) { - if (!session) - return NULL; - - assert(!session->public); - assert(!session->wants_control); - - if (session->name) { - hashmap_remove_value(session->seat->session_map, session->name, session); - hashmap_remove_value(session->seat->context->session_map, session->name, session); - } - - free(session->path); - free(session->name); - free(session); - - return NULL; -} - -void sysview_session_set_userdata(sysview_session *session, void *userdata) { - assert(session); - - session->userdata = userdata; -} - -void *sysview_session_get_userdata(sysview_session *session) { - assert_return(session, NULL); - - return session->userdata; -} - -const char *sysview_session_get_name(sysview_session *session) { - assert_return(session, NULL); - - return session->name; -} - -sysview_seat *sysview_session_get_seat(sysview_session *session) { - assert_return(session, NULL); - - return session->seat; -} - -static int session_take_control_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - sysview_session *session = userdata; - int r, error; - - session->slot_take_control = sd_bus_slot_unref(session->slot_take_control); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *e = sd_bus_message_get_error(reply); - - log_debug("sysview: %s: TakeControl failed: %s: %s", - session->name, e->name, e->message); - error = -sd_bus_error_get_errno(e); - } else { - session->has_control = true; - error = 0; - } - - r = context_raise_session_control(session->seat->context, session, error); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while signalling session control '%d' on session '%s': %m", - error, session->name); - - return 0; -} - -int sysview_session_take_control(sysview_session *session) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - assert_return(session, -EINVAL); - assert_return(!session->custom, -EINVAL); - - if (session->wants_control) - return 0; - - r = sd_bus_message_new_method_call(session->seat->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "TakeControl"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "b", 0); - if (r < 0) - return r; - - r = sd_bus_call_async(session->seat->context->sysbus, - &session->slot_take_control, - m, - session_take_control_fn, - session, - 0); - if (r < 0) - return r; - - session->wants_control = true; - return 0; -} - -void sysview_session_release_control(sysview_session *session) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - assert(session); - assert(!session->custom); - - if (!session->wants_control) - return; - - session->wants_control = false; - - if (!session->has_control && !session->slot_take_control) - return; - - session->has_control = false; - session->slot_take_control = sd_bus_slot_unref(session->slot_take_control); - - r = sd_bus_message_new_method_call(session->seat->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "ReleaseControl"); - if (r >= 0) - r = sd_bus_send(session->seat->context->sysbus, m, NULL); - - if (r < 0 && r != -ENOTCONN) - log_debug_errno(r, "sysview: %s: cannot send ReleaseControl: %m", - session->name); -} - -/* - * Seats - */ - -sysview_seat *sysview_find_seat(sysview_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->seat_map, name); -} - -int sysview_seat_new(sysview_seat **out, sysview_context *c, const char *name) { - _cleanup_(sysview_seat_freep) sysview_seat *seat = NULL; - int r; - - assert_return(c, -EINVAL); - assert_return(name, -EINVAL); - - seat = new0(sysview_seat, 1); - if (!seat) - return -ENOMEM; - - seat->context = c; - - seat->name = strdup(name); - if (!seat->name) - return -ENOMEM; - - r = sd_bus_path_encode("/org/freedesktop/login1/seat", seat->name, &seat->path); - if (r < 0) - return r; - - seat->session_map = hashmap_new(&string_hash_ops); - if (!seat->session_map) - return -ENOMEM; - - seat->device_map = hashmap_new(&string_hash_ops); - if (!seat->device_map) - return -ENOMEM; - - r = hashmap_put(c->seat_map, seat->name, seat); - if (r < 0) - return r; - - if (out) - *out = seat; - seat = NULL; - return 0; -} - -sysview_seat *sysview_seat_free(sysview_seat *seat) { - if (!seat) - return NULL; - - assert(!seat->public); - assert(hashmap_size(seat->device_map) == 0); - assert(hashmap_size(seat->session_map) == 0); - - if (seat->name) - hashmap_remove_value(seat->context->seat_map, seat->name, seat); - - hashmap_free(seat->device_map); - hashmap_free(seat->session_map); - free(seat->path); - free(seat->name); - free(seat); - - return NULL; -} - -const char *sysview_seat_get_name(sysview_seat *seat) { - assert_return(seat, NULL); - - return seat->name; -} - -int sysview_seat_switch_to(sysview_seat *seat, uint32_t nr) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - assert_return(seat, -EINVAL); - assert_return(seat->context->sysbus, -EINVAL); - - r = sd_bus_message_new_method_call(seat->context->sysbus, - &m, - "org.freedesktop.login1", - seat->path, - "org.freedesktop.login1.Seat", - "SwitchTo"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "u", nr); - if (r < 0) - return r; - - return sd_bus_send(seat->context->sysbus, m, NULL); -} - -/* - * Contexts - */ - -static int context_raise(sysview_context *c, sysview_event *event, int def) { - return c->running ? c->event_fn(c, c->userdata, event) : def; -} - -static int context_raise_settle(sysview_context *c) { - sysview_event event = { - .type = SYSVIEW_EVENT_SETTLE, - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_seat_add(sysview_context *c, sysview_seat *seat) { - sysview_event event = { - .type = SYSVIEW_EVENT_SEAT_ADD, - .seat_add = { - .seat = seat, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_seat_remove(sysview_context *c, sysview_seat *seat) { - sysview_event event = { - .type = SYSVIEW_EVENT_SEAT_REMOVE, - .seat_remove = { - .seat = seat, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_filter(sysview_context *c, - const char *id, - const char *seatid, - const char *username, - unsigned int uid) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_FILTER, - .session_filter = { - .id = id, - .seatid = seatid, - .username = username, - .uid = uid, - } - }; - - return context_raise(c, &event, 1); -} - -static int context_raise_session_add(sysview_context *c, sysview_session *session) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_ADD, - .session_add = { - .session = session, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_remove(sysview_context *c, sysview_session *session) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_REMOVE, - .session_remove = { - .session = session, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_control(sysview_context *c, sysview_session *session, int error) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_CONTROL, - .session_control = { - .session = session, - .error = error, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_attach(sysview_context *c, sysview_session *session, sysview_device *device) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_ATTACH, - .session_attach = { - .session = session, - .device = device, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_detach(sysview_context *c, sysview_session *session, sysview_device *device) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_DETACH, - .session_detach = { - .session = session, - .device = device, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_refresh(sysview_context *c, sysview_session *session, sysview_device *device, struct udev_device *ud) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_REFRESH, - .session_refresh = { - .session = session, - .device = device, - .ud = ud, - } - }; - - return context_raise(c, &event, 0); -} - -static void context_settle(sysview_context *c) { - int r; - - if (c->n_probe <= 0 || --c->n_probe > 0) - return; - - log_debug("sysview: settle"); - - c->settled = true; - - r = context_raise_settle(c); - if (r < 0) - log_debug_errno(r, "sysview: callback failed on settle: %m"); -} - -static void context_add_device(sysview_context *c, sysview_device *device) { - sysview_session *session; - Iterator i; - int r; - - assert(c); - assert(device); - - log_debug("sysview: add device '%s' on seat '%s'", - device->name, device->seat->name); - - HASHMAP_FOREACH(session, device->seat->session_map, i) { - if (!session->public) - continue; - - r = context_raise_session_attach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while attaching device '%s' to session '%s': %m", - device->name, session->name); - } -} - -static void context_remove_device(sysview_context *c, sysview_device *device) { - sysview_session *session; - Iterator i; - int r; - - assert(c); - assert(device); - - log_debug("sysview: remove device '%s'", device->name); - - HASHMAP_FOREACH(session, device->seat->session_map, i) { - if (!session->public) - continue; - - r = context_raise_session_detach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while detaching device '%s' from session '%s': %m", - device->name, session->name); - } - - sysview_device_free(device); -} - -static void context_change_device(sysview_context *c, sysview_device *device, struct udev_device *ud) { - sysview_session *session; - Iterator i; - int r; - - assert(c); - assert(device); - - log_debug("sysview: change device '%s'", device->name); - - HASHMAP_FOREACH(session, device->seat->session_map, i) { - if (!session->public) - continue; - - r = context_raise_session_refresh(c, session, device, ud); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while changing device '%s' on session '%s': %m", - device->name, session->name); - } -} - -static void context_add_session(sysview_context *c, sysview_seat *seat, const char *id) { - sysview_session *session; - sysview_device *device; - Iterator i; - int r; - - assert(c); - assert(seat); - assert(id); - - session = sysview_find_session(c, id); - if (session) - return; - - log_debug("sysview: add session '%s' on seat '%s'", id, seat->name); - - r = sysview_session_new(&session, seat, id); - if (r < 0) - goto error; - - if (!seat->scanned) { - r = sysview_context_rescan(c); - if (r < 0) - goto error; - } - - if (seat->public) { - session->public = true; - r = context_raise_session_add(c, session); - if (r < 0) { - log_debug_errno(r, "sysview: callback failed while adding session '%s': %m", - session->name); - session->public = false; - goto error; - } - - HASHMAP_FOREACH(device, seat->device_map, i) { - r = context_raise_session_attach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while attaching device '%s' to new session '%s': %m", - device->name, session->name); - } - } - - return; - -error: - if (r < 0) - log_debug_errno(r, "sysview: error while adding session '%s': %m", - id); -} - -static void context_remove_session(sysview_context *c, sysview_session *session) { - sysview_device *device; - Iterator i; - int r; - - assert(c); - assert(session); - - log_debug("sysview: remove session '%s'", session->name); - - if (session->public) { - HASHMAP_FOREACH(device, session->seat->device_map, i) { - r = context_raise_session_detach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while detaching device '%s' from old session '%s': %m", - device->name, session->name); - } - - session->public = false; - r = context_raise_session_remove(c, session); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while removing session '%s': %m", - session->name); - } - - if (!session->custom) - sysview_session_release_control(session); - - sysview_session_free(session); -} - -static void context_add_seat(sysview_context *c, const char *id) { - sysview_seat *seat; - int r; - - assert(c); - assert(id); - - seat = sysview_find_seat(c, id); - if (seat) - return; - - log_debug("sysview: add seat '%s'", id); - - r = sysview_seat_new(&seat, c, id); - if (r < 0) - goto error; - - seat->public = true; - r = context_raise_seat_add(c, seat); - if (r < 0) { - log_debug_errno(r, "sysview: callback failed while adding seat '%s': %m", - seat->name); - seat->public = false; - } - - return; - -error: - if (r < 0) - log_debug_errno(r, "sysview: error while adding seat '%s': %m", - id); -} - -static void context_remove_seat(sysview_context *c, sysview_seat *seat) { - sysview_session *session; - sysview_device *device; - int r; - - assert(c); - assert(seat); - - log_debug("sysview: remove seat '%s'", seat->name); - - while ((device = hashmap_first(seat->device_map))) - context_remove_device(c, device); - - while ((session = hashmap_first(seat->session_map))) - context_remove_session(c, session); - - if (seat->public) { - seat->public = false; - r = context_raise_seat_remove(c, seat); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while removing seat '%s': %m", - seat->name); - } - - sysview_seat_free(seat); -} - -int sysview_context_new(sysview_context **out, - unsigned int flags, - sd_event *event, - sd_bus *sysbus, - struct udev *ud) { - _cleanup_(sysview_context_freep) sysview_context *c = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(event, -EINVAL); - - log_debug("sysview: new"); - - c = new0(sysview_context, 1); - if (!c) - return -ENOMEM; - - c->event = sd_event_ref(event); - if (flags & SYSVIEW_CONTEXT_SCAN_LOGIND) - c->scan_logind = true; - if (flags & SYSVIEW_CONTEXT_SCAN_EVDEV) - c->scan_evdev = true; - if (flags & SYSVIEW_CONTEXT_SCAN_DRM) - c->scan_drm = true; - - if (sysbus) { - c->sysbus = sd_bus_ref(sysbus); - } else if (c->scan_logind) { - r = sd_bus_open_system(&c->sysbus); - if (r < 0) - return r; - } - - if (ud) { - c->ud = udev_ref(ud); - } else if (c->scan_evdev || c->scan_drm) { - errno = 0; - c->ud = udev_new(); - if (!c->ud) - return errno > 0 ? -errno : -EFAULT; - } - - c->seat_map = hashmap_new(&string_hash_ops); - if (!c->seat_map) - return -ENOMEM; - - c->session_map = hashmap_new(&string_hash_ops); - if (!c->session_map) - return -ENOMEM; - - c->device_map = hashmap_new(&string_hash_ops); - if (!c->device_map) - return -ENOMEM; - - *out = c; - c = NULL; - return 0; -} - -sysview_context *sysview_context_free(sysview_context *c) { - if (!c) - return NULL; - - log_debug("sysview: free"); - - sysview_context_stop(c); - - assert(hashmap_size(c->device_map) == 0); - assert(hashmap_size(c->session_map) == 0); - assert(hashmap_size(c->seat_map) == 0); - - hashmap_free(c->device_map); - hashmap_free(c->session_map); - hashmap_free(c->seat_map); - c->ud = udev_unref(c->ud); - c->sysbus = sd_bus_unref(c->sysbus); - c->event = sd_event_unref(c->event); - free(c); - - return NULL; -} - -static int context_ud_prepare_monitor(sysview_context *c, struct udev_monitor *m) { - int r; - - if (c->scan_evdev) { - r = udev_monitor_filter_add_match_subsystem_devtype(m, "input", NULL); - if (r < 0) - return r; - } - - if (c->scan_drm) { - r = udev_monitor_filter_add_match_subsystem_devtype(m, "drm", NULL); - if (r < 0) - return r; - } - - return 0; -} - -static int context_ud_prepare_scan(sysview_context *c, struct udev_enumerate *e) { - int r; - - if (c->scan_evdev) { - r = udev_enumerate_add_match_subsystem(e, "input"); - if (r < 0) - return r; - } - - if (c->scan_drm) { - r = udev_enumerate_add_match_subsystem(e, "drm"); - if (r < 0) - return r; - } - - r = udev_enumerate_add_match_is_initialized(e); - if (r < 0) - return r; - - return 0; -} - -static int context_ud_hotplug(sysview_context *c, struct udev_device *d) { - const char *syspath, *sysname, *subsystem, *action, *seatname; - sysview_device *device; - int r; - - syspath = udev_device_get_syspath(d); - sysname = udev_device_get_sysname(d); - subsystem = udev_device_get_subsystem(d); - action = udev_device_get_action(d); - - /* not interested in custom devices without syspath/etc */ - if (!syspath || !sysname || !subsystem) - return 0; - - device = sysview_find_device(c, syspath); - - if (streq_ptr(action, "remove")) { - if (!device) - return 0; - - context_remove_device(c, device); - } else if (streq_ptr(action, "change")) { - if (!device) - return 0; - - context_change_device(c, device, d); - } else if (!action || streq_ptr(action, "add")) { - struct udev_device *p; - unsigned int type, t; - sysview_seat *seat; - - if (device) - return 0; - - if (streq(subsystem, "input") && startswith(sysname, "event") && safe_atou(sysname + 5, &t) >= 0) - type = SYSVIEW_DEVICE_EVDEV; - else if (streq(subsystem, "drm") && startswith(sysname, "card")) - type = SYSVIEW_DEVICE_DRM; - else - type = (unsigned)-1; - - if (type >= SYSVIEW_DEVICE_CNT) - return 0; - - p = d; - seatname = NULL; - do { - seatname = udev_device_get_property_value(p, "ID_SEAT"); - if (seatname) - break; - } while ((p = udev_device_get_parent(p))); - - seat = sysview_find_seat(c, seatname ? : "seat0"); - if (!seat) - return 0; - - r = device_new_ud(&device, seat, type, d); - if (r < 0) - return log_debug_errno(r, "sysview: cannot create device for udev-device '%s': %m", - syspath); - - context_add_device(c, device); - } - - return 0; -} - -static int context_ud_monitor_fn(sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - sysview_context *c = userdata; - struct udev_device *d; - int r; - - if (revents & EPOLLIN) { - while ((d = udev_monitor_receive_device(c->ud_monitor))) { - r = context_ud_hotplug(c, d); - udev_device_unref(d); - if (r != 0) - return r; - } - - /* as long as EPOLLIN is signalled, read pending data */ - return 0; - } - - if (revents & (EPOLLHUP | EPOLLERR)) { - log_debug("sysview: HUP on udev-monitor"); - c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src); - } - - return 0; -} - -static int context_ud_start(sysview_context *c) { - int r, fd; - - if (!c->ud) - return 0; - - errno = 0; - c->ud_monitor = udev_monitor_new_from_netlink(c->ud, "udev"); - if (!c->ud_monitor) - return errno > 0 ? -errno : -EFAULT; - - r = context_ud_prepare_monitor(c, c->ud_monitor); - if (r < 0) - return r; - - r = udev_monitor_enable_receiving(c->ud_monitor); - if (r < 0) - return r; - - fd = udev_monitor_get_fd(c->ud_monitor); - r = sd_event_add_io(c->event, - &c->ud_monitor_src, - fd, - EPOLLHUP | EPOLLERR | EPOLLIN, - context_ud_monitor_fn, - c); - if (r < 0) - return r; - - return 0; -} - -static void context_ud_stop(sysview_context *c) { - c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src); - c->ud_monitor = udev_monitor_unref(c->ud_monitor); -} - -static int context_ud_scan(sysview_context *c) { - _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL; - struct udev_list_entry *entry; - struct udev_device *d; - int r; - - if (!c->ud_monitor) - return 0; - - errno = 0; - e = udev_enumerate_new(c->ud); - if (!e) - return errno > 0 ? -errno : -EFAULT; - - r = context_ud_prepare_scan(c, e); - if (r < 0) - return r; - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return r; - - udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { - const char *name; - - name = udev_list_entry_get_name(entry); - - errno = 0; - d = udev_device_new_from_syspath(c->ud, name); - if (!d) { - r = errno > 0 ? -errno : -EFAULT; - log_debug_errno(r, "sysview: cannot create udev-device for %s: %m", - name); - continue; - } - - r = context_ud_hotplug(c, d); - udev_device_unref(d); - if (r != 0) - return r; - } - - return 0; -} - -static int context_ld_seat_new(sysview_context *c, sd_bus_message *signal) { - const char *id, *path; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SeatNew from logind: %m"); - - context_add_seat(c, id); - return 0; -} - -static int context_ld_seat_removed(sysview_context *c, sd_bus_message *signal) { - const char *id, *path; - sysview_seat *seat; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SeatRemoved from logind: %m"); - - seat = sysview_find_seat(c, id); - if (!seat) - return 0; - - context_remove_seat(c, seat); - return 0; -} - -static int context_ld_session_new(sysview_context *c, sd_bus_message *signal) { - _cleanup_free_ char *seatid = NULL, *username = NULL; - const char *id, *path; - sysview_seat *seat; - uid_t uid; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SessionNew from logind: %m"); - - /* - * As the dbus message didn't contain enough information, we - * read missing bits via sd-login. Note that this might race session - * destruction, so we handle ENOENT properly. - */ - - /* ENOENT is also returned for sessions without seats */ - r = sd_session_get_seat(id, &seatid); - if (r == -ENOENT) - return 0; - else if (r < 0) - goto error; - - seat = sysview_find_seat(c, seatid); - if (!seat) - return 0; - - r = sd_session_get_uid(id, &uid); - if (r == -ENOENT) - return 0; - else if (r < 0) - goto error; - - username = lookup_uid(uid); - if (!username) { - r = -ENOMEM; - goto error; - } - - r = context_raise_session_filter(c, id, seatid, username, uid); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m", - id); - else if (r > 0) - context_add_session(c, seat, id); - - return 0; - -error: - return log_debug_errno(r, "sysview: failed retrieving information for new session '%s': %m", - id); -} - -static int context_ld_session_removed(sysview_context *c, sd_bus_message *signal) { - sysview_session *session; - const char *id, *path; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SessionRemoved from logind: %m"); - - session = sysview_find_session(c, id); - if (!session) - return 0; - - context_remove_session(c, session); - return 0; -} - -static int context_ld_manager_signal_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - sysview_context *c = userdata; - - if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatNew")) - return context_ld_seat_new(c, signal); - else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatRemoved")) - return context_ld_seat_removed(c, signal); - else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionNew")) - return context_ld_session_new(c, signal); - else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionRemoved")) - return context_ld_session_removed(c, signal); - else - return 0; -} - -static int context_ld_start(sysview_context *c) { - int r; - - if (!c->scan_logind) - return 0; - - r = sd_bus_add_match(c->sysbus, - &c->ld_slot_manager_signal, - "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Manager'," - "path='/org/freedesktop/login1'", - context_ld_manager_signal_fn, - c); - if (r < 0) - return r; - - return 0; -} - -static void context_ld_stop(sysview_context *c) { - c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions); - c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats); - c->ld_slot_manager_signal = sd_bus_slot_unref(c->ld_slot_manager_signal); -} - -static int context_ld_list_seats_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - sysview_context *c = userdata; - int r; - - c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("sysview: ListSeats on logind failed: %s: %s", - error->name, error->message); - r = -sd_bus_error_get_errno(error); - goto settle; - } - - r = sd_bus_message_enter_container(reply, 'a', "(so)"); - if (r < 0) - goto error; - - while ((r = sd_bus_message_enter_container(reply, 'r', "so")) > 0) { - const char *id, *path; - - r = sd_bus_message_read(reply, "so", &id, &path); - if (r < 0) - goto error; - - context_add_seat(c, id); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - } - - if (r < 0) - goto error; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - - r = 0; - goto settle; - -error: - log_debug_errno(r, "sysview: erroneous ListSeats response from logind: %m"); -settle: - context_settle(c); - return r; -} - -static int context_ld_list_sessions_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - sysview_context *c = userdata; - int r; - - c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("sysview: ListSessions on logind failed: %s: %s", - error->name, error->message); - r = -sd_bus_error_get_errno(error); - goto settle; - } - - r = sd_bus_message_enter_container(reply, 'a', "(susso)"); - if (r < 0) - goto error; - - while ((r = sd_bus_message_enter_container(reply, 'r', "susso")) > 0) { - const char *id, *username, *seatid, *path; - sysview_seat *seat; - unsigned int uid; - - r = sd_bus_message_read(reply, - "susso", - &id, - &uid, - &username, - &seatid, - &path); - if (r < 0) - goto error; - - seat = sysview_find_seat(c, seatid); - if (seat) { - r = context_raise_session_filter(c, id, seatid, username, uid); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m", - id); - else if (r > 0) - context_add_session(c, seat, id); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - } - - if (r < 0) - goto error; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - - r = 0; - goto settle; - -error: - log_debug_errno(r, "sysview: erroneous ListSessions response from logind: %m"); -settle: - context_settle(c); - return r; -} - -static int context_ld_scan(sysview_context *c) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - if (!c->ld_slot_manager_signal) - return 0; - - /* request seat list */ - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ListSeats"); - if (r < 0) - return r; - - r = sd_bus_call_async(c->sysbus, - &c->ld_slot_list_seats, - m, - context_ld_list_seats_fn, - c, - 0); - if (r < 0) - return r; - - if (!c->settled) - ++c->n_probe; - - /* request session list */ - - m = sd_bus_message_unref(m); - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ListSessions"); - if (r < 0) - return r; - - r = sd_bus_call_async(c->sysbus, - &c->ld_slot_list_sessions, - m, - context_ld_list_sessions_fn, - c, - 0); - if (r < 0) - return r; - - if (!c->settled) - ++c->n_probe; - - return 0; -} - -bool sysview_context_is_running(sysview_context *c) { - return c && c->running; -} - -int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata) { - int r; - - assert_return(c, -EINVAL); - assert_return(event_fn, -EINVAL); - - if (c->running) - return -EALREADY; - - log_debug("sysview: start"); - - c->running = true; - c->event_fn = event_fn; - c->userdata = userdata; - - r = context_ld_start(c); - if (r < 0) - goto error; - - r = context_ud_start(c); - if (r < 0) - goto error; - - r = sysview_context_rescan(c); - if (r < 0) - goto error; - - return 0; - -error: - sysview_context_stop(c); - return r; -} - -void sysview_context_stop(sysview_context *c) { - sysview_session *session; - sysview_device *device; - sysview_seat *seat; - - assert(c); - - if (!c->running) - return; - - log_debug("sysview: stop"); - - while ((device = hashmap_first(c->device_map))) - context_remove_device(c, device); - - while ((session = hashmap_first(c->session_map))) - context_remove_session(c, session); - - while ((seat = hashmap_first(c->seat_map))) - context_remove_seat(c, seat); - - c->running = false; - c->scanned = false; - c->settled = false; - c->n_probe = 0; - c->event_fn = NULL; - c->userdata = NULL; - c->scan_src = sd_event_source_unref(c->scan_src); - context_ud_stop(c); - context_ld_stop(c); -} - -static int context_scan_fn(sd_event_source *s, void *userdata) { - sysview_context *c = userdata; - sysview_seat *seat; - Iterator i; - int r; - - c->rescan = false; - - if (!c->scanned) { - r = context_ld_scan(c); - if (r < 0) - return log_debug_errno(r, "sysview: logind scan failed: %m"); - } - - /* skip device scans if no sessions are available */ - if (hashmap_size(c->session_map) > 0) { - r = context_ud_scan(c); - if (r < 0) - return log_debug_errno(r, "sysview: udev scan failed: %m"); - - HASHMAP_FOREACH(seat, c->seat_map, i) - seat->scanned = true; - } - - c->scanned = true; - context_settle(c); - - return 0; -} - -int sysview_context_rescan(sysview_context *c) { - assert(c); - - if (!c->running) - return 0; - - if (!c->rescan) { - c->rescan = true; - if (!c->settled) - ++c->n_probe; - } - - if (c->scan_src) - return sd_event_source_set_enabled(c->scan_src, SD_EVENT_ONESHOT); - else - return sd_event_add_defer(c->event, &c->scan_src, context_scan_fn, c); -} diff --git a/src/libsystemd-terminal/sysview.h b/src/libsystemd-terminal/sysview.h deleted file mode 100644 index a5e7a38df3..0000000000 --- a/src/libsystemd-terminal/sysview.h +++ /dev/null @@ -1,162 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * System View - * The sysview interface scans and monitors the system for seats, sessions and - * devices. It basically mirrors the state of logind on the application side. - * It's meant as base for session services that require managed device access. - * The logind controller API is employed to allow unprivileged access to all - * devices of a user. - * Furthermore, the sysview interface can be used for system services that run - * in situations where logind is not available, but session-like services are - * needed. For instance, the initrd does not run logind but might require - * graphics access. It cannot run session services, though. The sysview - * interface pretends that a session is available and provides the same - * interface as to normal session services. - */ - -#pragma once - -#include <stdbool.h> -#include "sd-bus.h" -#include "sd-event.h" - -typedef struct sysview_event sysview_event; -typedef struct sysview_device sysview_device; -typedef struct sysview_session sysview_session; -typedef struct sysview_seat sysview_seat; -typedef struct sysview_context sysview_context; - -/* - * Events - */ - -enum { - SYSVIEW_EVENT_SETTLE, - - SYSVIEW_EVENT_SEAT_ADD, - SYSVIEW_EVENT_SEAT_REMOVE, - - SYSVIEW_EVENT_SESSION_FILTER, - SYSVIEW_EVENT_SESSION_ADD, - SYSVIEW_EVENT_SESSION_REMOVE, - SYSVIEW_EVENT_SESSION_ATTACH, - SYSVIEW_EVENT_SESSION_DETACH, - SYSVIEW_EVENT_SESSION_REFRESH, - SYSVIEW_EVENT_SESSION_CONTROL, -}; - -struct sysview_event { - unsigned int type; - - union { - struct { - sysview_seat *seat; - } seat_add, seat_remove; - - struct { - const char *id; - const char *seatid; - const char *username; - unsigned int uid; - } session_filter; - - struct { - sysview_session *session; - } session_add, session_remove; - - struct { - sysview_session *session; - sysview_device *device; - } session_attach, session_detach; - - struct { - sysview_session *session; - sysview_device *device; - struct udev_device *ud; - } session_refresh; - - struct { - sysview_session *session; - int error; - } session_control; - }; -}; - -typedef int (*sysview_event_fn) (sysview_context *c, void *userdata, sysview_event *e); - -/* - * Devices - */ - -enum { - SYSVIEW_DEVICE_EVDEV, - SYSVIEW_DEVICE_DRM, - SYSVIEW_DEVICE_CNT -}; - -const char *sysview_device_get_name(sysview_device *device); -unsigned int sysview_device_get_type(sysview_device *device); -struct udev_device *sysview_device_get_ud(sysview_device *device); - -/* - * Sessions - */ - -void sysview_session_set_userdata(sysview_session *session, void *userdata); -void *sysview_session_get_userdata(sysview_session *session); - -const char *sysview_session_get_name(sysview_session *session); -sysview_seat *sysview_session_get_seat(sysview_session *session); - -int sysview_session_take_control(sysview_session *session); -void sysview_session_release_control(sysview_session *session); - -/* - * Seats - */ - -const char *sysview_seat_get_name(sysview_seat *seat); -int sysview_seat_switch_to(sysview_seat *seat, uint32_t nr); - -/* - * Contexts - */ - -enum { - SYSVIEW_CONTEXT_SCAN_LOGIND = (1 << 0), - SYSVIEW_CONTEXT_SCAN_EVDEV = (1 << 1), - SYSVIEW_CONTEXT_SCAN_DRM = (1 << 2), -}; - -int sysview_context_new(sysview_context **out, - unsigned int flags, - sd_event *event, - sd_bus *sysbus, - struct udev *ud); -sysview_context *sysview_context_free(sysview_context *c); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_context*, sysview_context_free); - -bool sysview_context_is_running(sysview_context *c); -int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata); -void sysview_context_stop(sysview_context *c); diff --git a/src/libsystemd-terminal/term-charset.c b/src/libsystemd-terminal/term-charset.c deleted file mode 100644 index 9db178861c..0000000000 --- a/src/libsystemd-terminal/term-charset.c +++ /dev/null @@ -1,488 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * VTE Character Sets - * These are predefined charactersets that can be loaded into GL and GR. By - * default we use unicode_lower and unicode_upper, that is, both sets have the - * exact unicode mapping. unicode_lower is effectively ASCII and unicode_upper - * as defined by the unicode standard (I guess, ISO 8859-1). - * Several other character sets are defined here. However, all of them are - * limited to the 96 character space of GL or GR. Everything beyond GR (which - * was not supported by the classic VTs by DEC but is available in VT emulators - * that support unicode/UTF8) is always mapped to unicode and cannot be changed - * by these character sets. Even mapping GL and GR is only available for - * backwards compatibility as new applications can use the Unicode functionality - * of the VTE. - * - * Moreover, mapping GR is almost unnecessary to support. In fact, Unicode UTF-8 - * support in VTE works by reading every incoming data as UTF-8 stream. This - * maps GL/ASCII to ASCII, as UTF-8 is backwards compatible to ASCII, however, - * everything that has the 8th bit set is a >=2-byte haracter in UTF-8. That is, - * this is in no way backwards compatible to >=VT220 8bit support. Therefore, if - * someone maps a character set into GR and wants to use them with this VTE, - * then they must already send UTF-8 characters to use GR (all GR characters are - * 8-bits). Hence, they can easily also send the correct UTF-8 character for the - * unicode mapping. - * The only advantage is that most characters in many sets are 3-byte UTF-8 - * characters and by mapping the set into GR/GL you can use 2 or 1 byte UTF-8 - * characters which saves bandwidth. - * Another reason is, if you have older applications that use the VT220 8-bit - * support and you put a ASCII/8bit-extension to UTF-8 converter in between, you - * need these mappings to have the application behave correctly if it uses GL/GR - * mappings extensively. - * - * Anyway, we support GL/GR mappings so here are the most commonly used maps as - * defined by Unicode-standard, DEC-private maps and other famous charmaps. - * - * Characters 1-32 are always the control characters (part of CL) and cannot be - * mapped. Characters 34-127 (94 characters) are part of GL and can be mapped. - * Characters 33 and 128 are not part of GL and always mapped by the VTE. - * However, for GR they can be mapped differently (96 chars) so we have to - * include them. The mapper has to take care not to use them in GL. - */ - -#include "term-internal.h" - -/* - * Lower Unicode character set. This maps the characters to the basic ASCII - * characters 33-126. These are all graphics characters defined in ASCII. - */ -term_charset term_unicode_lower = { - [0] = 32, - [1] = 33, - [2] = 34, - [3] = 35, - [4] = 36, - [5] = 37, - [6] = 38, - [7] = 39, - [8] = 40, - [9] = 41, - [10] = 42, - [11] = 43, - [12] = 44, - [13] = 45, - [14] = 46, - [15] = 47, - [16] = 48, - [17] = 49, - [18] = 50, - [19] = 51, - [20] = 52, - [21] = 53, - [22] = 54, - [23] = 55, - [24] = 56, - [25] = 57, - [26] = 58, - [27] = 59, - [28] = 60, - [29] = 61, - [30] = 62, - [31] = 63, - [32] = 64, - [33] = 65, - [34] = 66, - [35] = 67, - [36] = 68, - [37] = 69, - [38] = 70, - [39] = 71, - [40] = 72, - [41] = 73, - [42] = 74, - [43] = 75, - [44] = 76, - [45] = 77, - [46] = 78, - [47] = 79, - [48] = 80, - [49] = 81, - [50] = 82, - [51] = 83, - [52] = 84, - [53] = 85, - [54] = 86, - [55] = 87, - [56] = 88, - [57] = 89, - [58] = 90, - [59] = 91, - [60] = 92, - [61] = 93, - [62] = 94, - [63] = 95, - [64] = 96, - [65] = 97, - [66] = 98, - [67] = 99, - [68] = 100, - [69] = 101, - [70] = 102, - [71] = 103, - [72] = 104, - [73] = 105, - [74] = 106, - [75] = 107, - [76] = 108, - [77] = 109, - [78] = 110, - [79] = 111, - [80] = 112, - [81] = 113, - [82] = 114, - [83] = 115, - [84] = 116, - [85] = 117, - [86] = 118, - [87] = 119, - [88] = 120, - [89] = 121, - [90] = 122, - [91] = 123, - [92] = 124, - [93] = 125, - [94] = 126, - [95] = 127, -}; - -/* - * Upper Unicode Table - * This maps all characters to the upper unicode characters 161-254. These are - * not compatible to any older 8 bit character sets. See the Unicode standard - * for the definitions of each symbol. - */ -term_charset term_unicode_upper = { - [0] = 160, - [1] = 161, - [2] = 162, - [3] = 163, - [4] = 164, - [5] = 165, - [6] = 166, - [7] = 167, - [8] = 168, - [9] = 169, - [10] = 170, - [11] = 171, - [12] = 172, - [13] = 173, - [14] = 174, - [15] = 175, - [16] = 176, - [17] = 177, - [18] = 178, - [19] = 179, - [20] = 180, - [21] = 181, - [22] = 182, - [23] = 183, - [24] = 184, - [25] = 185, - [26] = 186, - [27] = 187, - [28] = 188, - [29] = 189, - [30] = 190, - [31] = 191, - [32] = 192, - [33] = 193, - [34] = 194, - [35] = 195, - [36] = 196, - [37] = 197, - [38] = 198, - [39] = 199, - [40] = 200, - [41] = 201, - [42] = 202, - [43] = 203, - [44] = 204, - [45] = 205, - [46] = 206, - [47] = 207, - [48] = 208, - [49] = 209, - [50] = 210, - [51] = 211, - [52] = 212, - [53] = 213, - [54] = 214, - [55] = 215, - [56] = 216, - [57] = 217, - [58] = 218, - [59] = 219, - [60] = 220, - [61] = 221, - [62] = 222, - [63] = 223, - [64] = 224, - [65] = 225, - [66] = 226, - [67] = 227, - [68] = 228, - [69] = 229, - [70] = 230, - [71] = 231, - [72] = 232, - [73] = 233, - [74] = 234, - [75] = 235, - [76] = 236, - [77] = 237, - [78] = 238, - [79] = 239, - [80] = 240, - [81] = 241, - [82] = 242, - [83] = 243, - [84] = 244, - [85] = 245, - [86] = 246, - [87] = 247, - [88] = 248, - [89] = 249, - [90] = 250, - [91] = 251, - [92] = 252, - [93] = 253, - [94] = 254, - [95] = 255, -}; - -/* - * The DEC supplemental graphics set. For its definition see here: - * http://vt100.net/docs/vt220-rm/table2-3b.html - * Its basically a mixture of common European symbols that are not part of - * ASCII. Most often, this is mapped into GR to extend the basci ASCII part. - * - * This is very similar to unicode_upper, however, few symbols differ so do not - * mix them up! - */ -term_charset term_dec_supplemental_graphics = { - [0] = -1, /* undefined */ - [1] = 161, - [2] = 162, - [3] = 163, - [4] = 0, - [5] = 165, - [6] = 0, - [7] = 167, - [8] = 164, - [9] = 169, - [10] = 170, - [11] = 171, - [12] = 0, - [13] = 0, - [14] = 0, - [15] = 0, - [16] = 176, - [17] = 177, - [18] = 178, - [19] = 179, - [20] = 0, - [21] = 181, - [22] = 182, - [23] = 183, - [24] = 0, - [25] = 185, - [26] = 186, - [27] = 187, - [28] = 188, - [29] = 189, - [30] = 0, - [31] = 191, - [32] = 192, - [33] = 193, - [34] = 194, - [35] = 195, - [36] = 196, - [37] = 197, - [38] = 198, - [39] = 199, - [40] = 200, - [41] = 201, - [42] = 202, - [43] = 203, - [44] = 204, - [45] = 205, - [46] = 206, - [47] = 207, - [48] = 0, - [49] = 209, - [50] = 210, - [51] = 211, - [52] = 212, - [53] = 213, - [54] = 214, - [55] = 338, - [56] = 216, - [57] = 217, - [58] = 218, - [59] = 219, - [60] = 220, - [61] = 376, - [62] = 0, - [63] = 223, - [64] = 224, - [65] = 225, - [66] = 226, - [67] = 227, - [68] = 228, - [69] = 229, - [70] = 230, - [71] = 231, - [72] = 232, - [73] = 233, - [74] = 234, - [75] = 235, - [76] = 236, - [77] = 237, - [78] = 238, - [79] = 239, - [80] = 0, - [81] = 241, - [82] = 242, - [83] = 243, - [84] = 244, - [85] = 245, - [86] = 246, - [87] = 339, - [88] = 248, - [89] = 249, - [90] = 250, - [91] = 251, - [92] = 252, - [93] = 255, - [94] = 0, - [95] = -1, /* undefined */ -}; - -/* - * DEC special graphics character set. See here for its definition: - * http://vt100.net/docs/vt220-rm/table2-4.html - * This contains several characters to create ASCII drawings and similar. Its - * commonly mapped into GR to extend the basic ASCII characters. - * - * Lower 62 characters map to ASCII 33-64, everything beyond is special and - * commonly used for ASCII drawings. It depends on the Unicode Standard 3.2 for - * the extended horizontal scan-line characters 3, 5, 7, and 9. - */ -term_charset term_dec_special_graphics = { - [0] = -1, /* undefined */ - [1] = 33, - [2] = 34, - [3] = 35, - [4] = 36, - [5] = 37, - [6] = 38, - [7] = 39, - [8] = 40, - [9] = 41, - [10] = 42, - [11] = 43, - [12] = 44, - [13] = 45, - [14] = 46, - [15] = 47, - [16] = 48, - [17] = 49, - [18] = 50, - [19] = 51, - [20] = 52, - [21] = 53, - [22] = 54, - [23] = 55, - [24] = 56, - [25] = 57, - [26] = 58, - [27] = 59, - [28] = 60, - [29] = 61, - [30] = 62, - [31] = 63, - [32] = 64, - [33] = 65, - [34] = 66, - [35] = 67, - [36] = 68, - [37] = 69, - [38] = 70, - [39] = 71, - [40] = 72, - [41] = 73, - [42] = 74, - [43] = 75, - [44] = 76, - [45] = 77, - [46] = 78, - [47] = 79, - [48] = 80, - [49] = 81, - [50] = 82, - [51] = 83, - [52] = 84, - [53] = 85, - [54] = 86, - [55] = 87, - [56] = 88, - [57] = 89, - [58] = 90, - [59] = 91, - [60] = 92, - [61] = 93, - [62] = 94, - [63] = 0, - [64] = 9830, - [65] = 9618, - [66] = 9225, - [67] = 9228, - [68] = 9229, - [69] = 9226, - [70] = 176, - [71] = 177, - [72] = 9252, - [73] = 9227, - [74] = 9496, - [75] = 9488, - [76] = 9484, - [77] = 9492, - [78] = 9532, - [79] = 9146, - [80] = 9147, - [81] = 9472, - [82] = 9148, - [83] = 9149, - [84] = 9500, - [85] = 9508, - [86] = 9524, - [87] = 9516, - [88] = 9474, - [89] = 8804, - [90] = 8805, - [91] = 960, - [92] = 8800, - [93] = 163, - [94] = 8901, - [95] = -1, /* undefined */ -}; diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h deleted file mode 100644 index 8c6a00188c..0000000000 --- a/src/libsystemd-terminal/term-internal.h +++ /dev/null @@ -1,650 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include "term.h" -#include "util.h" - -typedef struct term_char term_char_t; -typedef struct term_charbuf term_charbuf_t; - -typedef struct term_cell term_cell; -typedef struct term_line term_line; - -typedef struct term_page term_page; -typedef struct term_history term_history; - -typedef uint32_t term_charset[96]; -typedef struct term_state term_state; - -/* - * Miscellaneous - * Sundry things and external helpers. - */ - -int mk_wcwidth(wchar_t ucs4); -int mk_wcwidth_cjk(wchar_t ucs4); -int mk_wcswidth(const wchar_t *str, size_t len); -int mk_wcswidth_cjk(const wchar_t *str, size_t len); - -/* - * Characters - * Each cell in a terminal page contains only a single character. This is - * usually a single UCS-4 value. However, Unicode allows combining-characters, - * therefore, the number of UCS-4 characters per cell must be unlimited. The - * term_char_t object wraps the internal combining char API so it can be - * treated as a single object. - */ - -struct term_char { - /* never access this value directly */ - uint64_t _value; -}; - -struct term_charbuf { - /* 3 bytes + zero-terminator */ - uint32_t buf[4]; -}; - -#define TERM_CHAR_INIT(_val) ((term_char_t){ ._value = (_val) }) -#define TERM_CHAR_NULL TERM_CHAR_INIT(0) - -term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4); -term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4); -term_char_t term_char_dup(term_char_t ch); -term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4); - -const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b); -unsigned int term_char_lookup_width(term_char_t ch); - -/* true if @ch is TERM_CHAR_NULL, otherwise false */ -static inline bool term_char_is_null(term_char_t ch) { - return ch._value == 0; -} - -/* true if @ch is dynamically allocated and needs to be freed */ -static inline bool term_char_is_allocated(term_char_t ch) { - return !term_char_is_null(ch) && !(ch._value & 0x1); -} - -/* true if (a == b), otherwise false; this is (a == b), NOT (*a == *b) */ -static inline bool term_char_same(term_char_t a, term_char_t b) { - return a._value == b._value; -} - -/* true if (*a == *b), otherwise false; this is implied by (a == b) */ -static inline bool term_char_equal(term_char_t a, term_char_t b) { - const uint32_t *sa, *sb; - term_charbuf_t ca, cb; - size_t na, nb; - - sa = term_char_resolve(a, &na, &ca); - sb = term_char_resolve(b, &nb, &cb); - return na == nb && !memcmp(sa, sb, sizeof(*sa) * na); -} - -/* free @ch in case it is dynamically allocated */ -static inline term_char_t term_char_free(term_char_t ch) { - if (term_char_is_allocated(ch)) - term_char_set(ch, 0); - - return TERM_CHAR_NULL; -} - -/* gcc _cleanup_ helpers */ -#define _term_char_free_ _cleanup_(term_char_freep) -static inline void term_char_freep(term_char_t *p) { - term_char_free(*p); -} - -/* - * Cells - * The term_cell structure respresents a single cell in a terminal page. It - * contains the stored character, the age of the cell and all its attributes. - */ - -struct term_cell { - term_char_t ch; /* stored char or TERM_CHAR_NULL */ - term_age_t age; /* cell age or TERM_AGE_NULL */ - term_attr attr; /* cell attributes */ - unsigned int cwidth; /* cached term_char_lookup_width(cell->ch) */ -}; - -/* - * Lines - * Instead of storing cells in a 2D array, we store them in an array of - * dynamically allocated lines. This way, scrolling can be implemented very - * fast without moving any cells at all. Similarly, the scrollback-buffer is - * much simpler to implement. - * We use term_line to store a single line. It contains an array of cells, a - * fill-state which remembers the amount of blanks on the right side, a - * separate age just for the line which can overwrite the age for all cells, - * and some management data. - */ - -struct term_line { - term_line *lines_next; /* linked-list for histories */ - term_line *lines_prev; /* linked-list for histories */ - - unsigned int width; /* visible width of line */ - unsigned int n_cells; /* # of allocated cells */ - term_cell *cells; /* cell-array */ - - term_age_t age; /* line age */ - unsigned int fill; /* # of valid cells; starting left */ -}; - -int term_line_new(term_line **out); -term_line *term_line_free(term_line *line); - -#define _term_line_free_ _cleanup_(term_line_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_line*, term_line_free); - -int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width); -void term_line_set_width(term_line *line, unsigned int width); -void term_line_write(term_line *line, unsigned int pos_x, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode); -void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age); -void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age); -void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age); -void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected); -void term_line_reset(term_line *line, const term_attr *attr, term_age_t age); - -void term_line_link(term_line *line, term_line **first, term_line **last); -void term_line_link_tail(term_line *line, term_line **first, term_line **last); -void term_line_unlink(term_line *line, term_line **first, term_line **last); - -#define TERM_LINE_LINK(_line, _head) term_line_link((_line), &(_head)->lines_first, &(_head)->lines_last) -#define TERM_LINE_LINK_TAIL(_line, _head) term_line_link_tail((_line), &(_head)->lines_first, &(_head)->lines_last) -#define TERM_LINE_UNLINK(_line, _head) term_line_unlink((_line), &(_head)->lines_first, &(_head)->lines_last) - -/* - * Pages - * A page represents the 2D table containing all cells of a terminal. It stores - * lines as an array of pointers so scrolling becomes a simple line-shuffle - * operation. - * Scrolling is always targeted only at the scroll-region defined via scroll_idx - * and scroll_num. The fill-state keeps track of the number of touched lines in - * the scroll-region. @width and @height describe the visible region of the page - * and are guaranteed to be allocated at all times. - */ - -struct term_page { - term_age_t age; /* page age */ - - term_line **lines; /* array of line-pointers */ - term_line **line_cache; /* cache for temporary operations */ - unsigned int n_lines; /* # of allocated lines */ - - unsigned int width; /* width of visible area */ - unsigned int height; /* height of visible area */ - unsigned int scroll_idx; /* scrolling-region start index */ - unsigned int scroll_num; /* scrolling-region length in lines */ - unsigned int scroll_fill; /* # of valid scroll-lines */ -}; - -int term_page_new(term_page **out); -term_page *term_page_free(term_page *page); - -#define _term_page_free_ _cleanup_(term_page_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_page*, term_page_free); - -term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y); - -int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age); -void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history); -void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode); -void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age); -void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age); -void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age); -void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected); -void term_page_reset(term_page *page, const term_attr *attr, term_age_t age); - -void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num); -void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history); -void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history); -void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age); -void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age); - -/* - * Histories - * Scroll-back buffers use term_history objects to store scroll-back lines. A - * page is independent of the history used. All page operations that modify a - * history take it as separate argument. You're free to pass NULL at all times - * if no history should be used. - * Lines are stored in a linked list as no complex operations are ever done on - * history lines, besides pushing/poping. Note that history lines do not have a - * guaranteed minimum length. Any kind of line might be stored there. Missing - * cells should be cleared to the background color. - */ - -struct term_history { - term_line *lines_first; - term_line *lines_last; - unsigned int n_lines; - unsigned int max_lines; -}; - -int term_history_new(term_history **out); -term_history *term_history_free(term_history *history); - -#define _term_history_free_ _cleanup_(term_history_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_history*, term_history_free); - -void term_history_clear(term_history *history); -void term_history_trim(term_history *history, unsigned int max); -void term_history_push(term_history *history, term_line *line); -term_line *term_history_pop(term_history *history, unsigned int reserve_width, const term_attr *attr, term_age_t age); -unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age); - -/* - * Parsers - * The term_parser object parses control-sequences for both host and terminal - * side. Based on this parser, there is a set of command-parsers that take a - * term_seq sequence and returns the command it represents. This is different - * for host and terminal side so a different set of parsers is provided. - */ - -enum { - TERM_SEQ_NONE, /* placeholder, no sequence parsed */ - - TERM_SEQ_IGNORE, /* no-op character */ - TERM_SEQ_GRAPHIC, /* graphic character */ - TERM_SEQ_CONTROL, /* control character */ - TERM_SEQ_ESCAPE, /* escape sequence */ - TERM_SEQ_CSI, /* control sequence function */ - TERM_SEQ_DCS, /* device control string */ - TERM_SEQ_OSC, /* operating system control */ - - TERM_SEQ_CNT -}; - -enum { - /* these must be kept compatible to (1U << (ch - 0x20)) */ - - TERM_SEQ_FLAG_SPACE = (1U << 0), /* char: */ - TERM_SEQ_FLAG_BANG = (1U << 1), /* char: ! */ - TERM_SEQ_FLAG_DQUOTE = (1U << 2), /* char: " */ - TERM_SEQ_FLAG_HASH = (1U << 3), /* char: # */ - TERM_SEQ_FLAG_CASH = (1U << 4), /* char: $ */ - TERM_SEQ_FLAG_PERCENT = (1U << 5), /* char: % */ - TERM_SEQ_FLAG_AND = (1U << 6), /* char: & */ - TERM_SEQ_FLAG_SQUOTE = (1U << 7), /* char: ' */ - TERM_SEQ_FLAG_POPEN = (1U << 8), /* char: ( */ - TERM_SEQ_FLAG_PCLOSE = (1U << 9), /* char: ) */ - TERM_SEQ_FLAG_MULT = (1U << 10), /* char: * */ - TERM_SEQ_FLAG_PLUS = (1U << 11), /* char: + */ - TERM_SEQ_FLAG_COMMA = (1U << 12), /* char: , */ - TERM_SEQ_FLAG_MINUS = (1U << 13), /* char: - */ - TERM_SEQ_FLAG_DOT = (1U << 14), /* char: . */ - TERM_SEQ_FLAG_SLASH = (1U << 15), /* char: / */ - - /* 16-35 is reserved for numbers; unused */ - - /* COLON is reserved = (1U << 26), char: : */ - /* SEMICOLON is reserved = (1U << 27), char: ; */ - TERM_SEQ_FLAG_LT = (1U << 28), /* char: < */ - TERM_SEQ_FLAG_EQUAL = (1U << 29), /* char: = */ - TERM_SEQ_FLAG_GT = (1U << 30), /* char: > */ - TERM_SEQ_FLAG_WHAT = (1U << 31), /* char: ? */ -}; - -enum { - TERM_CMD_NONE, /* placeholder */ - TERM_CMD_GRAPHIC, /* graphics character */ - - TERM_CMD_BEL, /* bell */ - TERM_CMD_BS, /* backspace */ - TERM_CMD_CBT, /* cursor-backward-tabulation */ - TERM_CMD_CHA, /* cursor-horizontal-absolute */ - TERM_CMD_CHT, /* cursor-horizontal-forward-tabulation */ - TERM_CMD_CNL, /* cursor-next-line */ - TERM_CMD_CPL, /* cursor-previous-line */ - TERM_CMD_CR, /* carriage-return */ - TERM_CMD_CUB, /* cursor-backward */ - TERM_CMD_CUD, /* cursor-down */ - TERM_CMD_CUF, /* cursor-forward */ - TERM_CMD_CUP, /* cursor-position */ - TERM_CMD_CUU, /* cursor-up */ - TERM_CMD_DA1, /* primary-device-attributes */ - TERM_CMD_DA2, /* secondary-device-attributes */ - TERM_CMD_DA3, /* tertiary-device-attributes */ - TERM_CMD_DC1, /* device-control-1 or XON */ - TERM_CMD_DC3, /* device-control-3 or XOFF */ - TERM_CMD_DCH, /* delete-character */ - TERM_CMD_DECALN, /* screen-alignment-pattern */ - TERM_CMD_DECANM, /* ansi-mode */ - TERM_CMD_DECBI, /* back-index */ - TERM_CMD_DECCARA, /* change-attributes-in-rectangular-area */ - TERM_CMD_DECCRA, /* copy-rectangular-area */ - TERM_CMD_DECDC, /* delete-column */ - TERM_CMD_DECDHL_BH, /* double-width-double-height-line: bottom half */ - TERM_CMD_DECDHL_TH, /* double-width-double-height-line: top half */ - TERM_CMD_DECDWL, /* double-width-single-height-line */ - TERM_CMD_DECEFR, /* enable-filter-rectangle */ - TERM_CMD_DECELF, /* enable-local-functions */ - TERM_CMD_DECELR, /* enable-locator-reporting */ - TERM_CMD_DECERA, /* erase-rectangular-area */ - TERM_CMD_DECFI, /* forward-index */ - TERM_CMD_DECFRA, /* fill-rectangular-area */ - TERM_CMD_DECIC, /* insert-column */ - TERM_CMD_DECID, /* return-terminal-id */ - TERM_CMD_DECINVM, /* invoke-macro */ - TERM_CMD_DECKBD, /* keyboard-language-selection */ - TERM_CMD_DECKPAM, /* keypad-application-mode */ - TERM_CMD_DECKPNM, /* keypad-numeric-mode */ - TERM_CMD_DECLFKC, /* local-function-key-control */ - TERM_CMD_DECLL, /* load-leds */ - TERM_CMD_DECLTOD, /* load-time-of-day */ - TERM_CMD_DECPCTERM, /* pcterm-mode */ - TERM_CMD_DECPKA, /* program-key-action */ - TERM_CMD_DECPKFMR, /* program-key-free-memory-report */ - TERM_CMD_DECRARA, /* reverse-attributes-in-rectangular-area */ - TERM_CMD_DECRC, /* restore-cursor */ - TERM_CMD_DECREQTPARM, /* request-terminal-parameters */ - TERM_CMD_DECRPKT, /* report-key-type */ - TERM_CMD_DECRQCRA, /* request-checksum-of-rectangular-area */ - TERM_CMD_DECRQDE, /* request-display-extent */ - TERM_CMD_DECRQKT, /* request-key-type */ - TERM_CMD_DECRQLP, /* request-locator-position */ - TERM_CMD_DECRQM_ANSI, /* request-mode-ansi */ - TERM_CMD_DECRQM_DEC, /* request-mode-dec */ - TERM_CMD_DECRQPKFM, /* request-program-key-free-memory */ - TERM_CMD_DECRQPSR, /* request-presentation-state-report */ - TERM_CMD_DECRQTSR, /* request-terminal-state-report */ - TERM_CMD_DECRQUPSS, /* request-user-preferred-supplemental-set */ - TERM_CMD_DECSACE, /* select-attribute-change-extent */ - TERM_CMD_DECSASD, /* select-active-status-display */ - TERM_CMD_DECSC, /* save-cursor */ - TERM_CMD_DECSCA, /* select-character-protection-attribute */ - TERM_CMD_DECSCL, /* select-conformance-level */ - TERM_CMD_DECSCP, /* select-communication-port */ - TERM_CMD_DECSCPP, /* select-columns-per-page */ - TERM_CMD_DECSCS, /* select-communication-speed */ - TERM_CMD_DECSCUSR, /* set-cursor-style */ - TERM_CMD_DECSDDT, /* select-disconnect-delay-time */ - TERM_CMD_DECSDPT, /* select-digital-printed-data-type */ - TERM_CMD_DECSED, /* selective-erase-in-display */ - TERM_CMD_DECSEL, /* selective-erase-in-line */ - TERM_CMD_DECSERA, /* selective-erase-rectangular-area */ - TERM_CMD_DECSFC, /* select-flow-control */ - TERM_CMD_DECSKCV, /* set-key-click-volume */ - TERM_CMD_DECSLCK, /* set-lock-key-style */ - TERM_CMD_DECSLE, /* select-locator-events */ - TERM_CMD_DECSLPP, /* set-lines-per-page */ - TERM_CMD_DECSLRM_OR_SC, /* set-left-and-right-margins or save-cursor */ - TERM_CMD_DECSMBV, /* set-margin-bell-volume */ - TERM_CMD_DECSMKR, /* select-modifier-key-reporting */ - TERM_CMD_DECSNLS, /* set-lines-per-screen */ - TERM_CMD_DECSPP, /* set-port-parameter */ - TERM_CMD_DECSPPCS, /* select-pro-printer-character-set */ - TERM_CMD_DECSPRTT, /* select-printer-type */ - TERM_CMD_DECSR, /* secure-reset */ - TERM_CMD_DECSRFR, /* select-refresh-rate */ - TERM_CMD_DECSSCLS, /* set-scroll-speed */ - TERM_CMD_DECSSDT, /* select-status-display-line-type */ - TERM_CMD_DECSSL, /* select-setup-language */ - TERM_CMD_DECST8C, /* set-tab-at-every-8-columns */ - TERM_CMD_DECSTBM, /* set-top-and-bottom-margins */ - TERM_CMD_DECSTR, /* soft-terminal-reset */ - TERM_CMD_DECSTRL, /* set-transmit-rate-limit */ - TERM_CMD_DECSWBV, /* set-warning-bell-volume */ - TERM_CMD_DECSWL, /* single-width-single-height-line */ - TERM_CMD_DECTID, /* select-terminal-id */ - TERM_CMD_DECTME, /* terminal-mode-emulation */ - TERM_CMD_DECTST, /* invoke-confidence-test */ - TERM_CMD_DL, /* delete-line */ - TERM_CMD_DSR_ANSI, /* device-status-report-ansi */ - TERM_CMD_DSR_DEC, /* device-status-report-dec */ - TERM_CMD_ECH, /* erase-character */ - TERM_CMD_ED, /* erase-in-display */ - TERM_CMD_EL, /* erase-in-line */ - TERM_CMD_ENQ, /* enquiry */ - TERM_CMD_EPA, /* end-of-guarded-area */ - TERM_CMD_FF, /* form-feed */ - TERM_CMD_HPA, /* horizontal-position-absolute */ - TERM_CMD_HPR, /* horizontal-position-relative */ - TERM_CMD_HT, /* horizontal-tab */ - TERM_CMD_HTS, /* horizontal-tab-set */ - TERM_CMD_HVP, /* horizontal-and-vertical-position */ - TERM_CMD_ICH, /* insert-character */ - TERM_CMD_IL, /* insert-line */ - TERM_CMD_IND, /* index */ - TERM_CMD_LF, /* line-feed */ - TERM_CMD_LS1R, /* locking-shift-1-right */ - TERM_CMD_LS2, /* locking-shift-2 */ - TERM_CMD_LS2R, /* locking-shift-2-right */ - TERM_CMD_LS3, /* locking-shift-3 */ - TERM_CMD_LS3R, /* locking-shift-3-right */ - TERM_CMD_MC_ANSI, /* media-copy-ansi */ - TERM_CMD_MC_DEC, /* media-copy-dec */ - TERM_CMD_NEL, /* next-line */ - TERM_CMD_NP, /* next-page */ - TERM_CMD_NULL, /* null */ - TERM_CMD_PP, /* preceding-page */ - TERM_CMD_PPA, /* page-position-absolute */ - TERM_CMD_PPB, /* page-position-backward */ - TERM_CMD_PPR, /* page-position-relative */ - TERM_CMD_RC, /* restore-cursor */ - TERM_CMD_REP, /* repeat */ - TERM_CMD_RI, /* reverse-index */ - TERM_CMD_RIS, /* reset-to-initial-state */ - TERM_CMD_RM_ANSI, /* reset-mode-ansi */ - TERM_CMD_RM_DEC, /* reset-mode-dec */ - TERM_CMD_S7C1T, /* set-7bit-c1-terminal */ - TERM_CMD_S8C1T, /* set-8bit-c1-terminal */ - TERM_CMD_SCS, /* select-character-set */ - TERM_CMD_SD, /* scroll-down */ - TERM_CMD_SGR, /* select-graphics-rendition */ - TERM_CMD_SI, /* shift-in */ - TERM_CMD_SM_ANSI, /* set-mode-ansi */ - TERM_CMD_SM_DEC, /* set-mode-dec */ - TERM_CMD_SO, /* shift-out */ - TERM_CMD_SPA, /* start-of-protected-area */ - TERM_CMD_SS2, /* single-shift-2 */ - TERM_CMD_SS3, /* single-shift-3 */ - TERM_CMD_ST, /* string-terminator */ - TERM_CMD_SU, /* scroll-up */ - TERM_CMD_SUB, /* substitute */ - TERM_CMD_TBC, /* tab-clear */ - TERM_CMD_VPA, /* vertical-line-position-absolute */ - TERM_CMD_VPR, /* vertical-line-position-relative */ - TERM_CMD_VT, /* vertical-tab */ - TERM_CMD_XTERM_CLLHP, /* xterm-cursor-lower-left-hp-bugfix */ - TERM_CMD_XTERM_IHMT, /* xterm-initiate-highlight-mouse-tracking */ - TERM_CMD_XTERM_MLHP, /* xterm-memory-lock-hp-bugfix */ - TERM_CMD_XTERM_MUHP, /* xterm-memory-unlock-hp-bugfix */ - TERM_CMD_XTERM_RPM, /* xterm-restore-private-mode */ - TERM_CMD_XTERM_RRV, /* xterm-reset-resource-value */ - TERM_CMD_XTERM_RTM, /* xterm-reset-title-mode */ - TERM_CMD_XTERM_SACL1, /* xterm-set-ansi-conformance-level-1 */ - TERM_CMD_XTERM_SACL2, /* xterm-set-ansi-conformance-level-2 */ - TERM_CMD_XTERM_SACL3, /* xterm-set-ansi-conformance-level-3 */ - TERM_CMD_XTERM_SDCS, /* xterm-set-default-character-set */ - TERM_CMD_XTERM_SGFX, /* xterm-sixel-graphics */ - TERM_CMD_XTERM_SPM, /* xterm-set-private-mode */ - TERM_CMD_XTERM_SRV, /* xterm-set-resource-value */ - TERM_CMD_XTERM_STM, /* xterm-set-title-mode */ - TERM_CMD_XTERM_SUCS, /* xterm-set-utf8-character-set */ - TERM_CMD_XTERM_WM, /* xterm-window-management */ - - TERM_CMD_CNT -}; - -enum { - /* - * Charsets: DEC marks charsets according to "Digital Equ. Corp.". - * NRCS marks charsets according to the "National Replacement - * Character Sets". ISO marks charsets according to ISO-8859. - * The USERDEF charset is special and can be modified by the host. - */ - - TERM_CHARSET_NONE, - - /* 96-compat charsets */ - TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL, - TERM_CHARSET_BRITISH_NRCS = TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL, - TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL, - TERM_CHARSET_AMERICAN_NRCS = TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL, - TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL, - TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL, - TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL, - TERM_CHARSET_ISO_LATIN_CYRILLIC, - - TERM_CHARSET_96_CNT, - - /* 94-compat charsets */ - TERM_CHARSET_DEC_SPECIAL_GRAPHIC = TERM_CHARSET_96_CNT, - TERM_CHARSET_DEC_SUPPLEMENTAL, - TERM_CHARSET_DEC_TECHNICAL, - TERM_CHARSET_CYRILLIC_DEC, - TERM_CHARSET_DUTCH_NRCS, - TERM_CHARSET_FINNISH_NRCS, - TERM_CHARSET_FRENCH_NRCS, - TERM_CHARSET_FRENCH_CANADIAN_NRCS, - TERM_CHARSET_GERMAN_NRCS, - TERM_CHARSET_GREEK_DEC, - TERM_CHARSET_GREEK_NRCS, - TERM_CHARSET_HEBREW_DEC, - TERM_CHARSET_HEBREW_NRCS, - TERM_CHARSET_ITALIAN_NRCS, - TERM_CHARSET_NORWEGIAN_DANISH_NRCS, - TERM_CHARSET_PORTUGUESE_NRCS, - TERM_CHARSET_RUSSIAN_NRCS, - TERM_CHARSET_SCS_NRCS, - TERM_CHARSET_SPANISH_NRCS, - TERM_CHARSET_SWEDISH_NRCS, - TERM_CHARSET_SWISS_NRCS, - TERM_CHARSET_TURKISH_DEC, - TERM_CHARSET_TURKISH_NRCS, - - TERM_CHARSET_94_CNT, - - /* special charsets */ - TERM_CHARSET_USERPREF_SUPPLEMENTAL = TERM_CHARSET_94_CNT, - - TERM_CHARSET_CNT, -}; - -extern term_charset term_unicode_lower; -extern term_charset term_unicode_upper; -extern term_charset term_dec_supplemental_graphics; -extern term_charset term_dec_special_graphics; - -#define TERM_PARSER_ARG_MAX (16) -#define TERM_PARSER_ST_MAX (4096) - -struct term_seq { - unsigned int type; - unsigned int command; - uint32_t terminator; - unsigned int intermediates; - unsigned int charset; - unsigned int n_args; - int args[TERM_PARSER_ARG_MAX]; - unsigned int n_st; - char *st; -}; - -struct term_parser { - term_seq seq; - size_t st_alloc; - unsigned int state; - - bool is_host : 1; -}; - -/* - * Screens - * A term_screen object represents the terminal-side of the communication. It - * connects the term-parser and term-pages and handles all required commands. - * All state is managed by it. - */ - -enum { - TERM_FLAG_7BIT_MODE = (1U << 0), /* 7bit mode (default: on) */ - TERM_FLAG_HIDE_CURSOR = (1U << 1), /* hide cursor caret (default: off) */ - TERM_FLAG_INHIBIT_TPARM = (1U << 2), /* do not send TPARM unrequested (default: off) */ - TERM_FLAG_NEWLINE_MODE = (1U << 3), /* perform carriage-return on line-feeds (default: off) */ - TERM_FLAG_PENDING_WRAP = (1U << 4), /* wrap-around is pending */ - TERM_FLAG_KEYPAD_MODE = (1U << 5), /* application-keypad mode (default: off) */ - TERM_FLAG_CURSOR_KEYS = (1U << 6), /* enable application cursor-keys (default: off) */ -}; - -enum { - TERM_CONFORMANCE_LEVEL_VT52, - TERM_CONFORMANCE_LEVEL_VT100, - TERM_CONFORMANCE_LEVEL_VT400, - TERM_CONFORMANCE_LEVEL_CNT, -}; - -struct term_state { - unsigned int cursor_x; - unsigned int cursor_y; - term_attr attr; - term_charset **gl; - term_charset **gr; - term_charset **glt; - term_charset **grt; - - bool auto_wrap : 1; - bool origin_mode : 1; -}; - -struct term_screen { - unsigned long ref; - term_age_t age; - - term_page *page; - term_page *page_main; - term_page *page_alt; - term_history *history; - term_history *history_main; - - unsigned int n_tabs; - uint8_t *tabs; - - term_utf8 utf8; - term_parser *parser; - - term_screen_write_fn write_fn; - void *write_fn_data; - term_screen_cmd_fn cmd_fn; - void *cmd_fn_data; - - unsigned int flags; - unsigned int conformance_level; - term_attr default_attr; - - term_charset *g0; - term_charset *g1; - term_charset *g2; - term_charset *g3; - - char *answerback; - - term_state state; - term_state saved; - term_state saved_alt; -}; diff --git a/src/libsystemd-terminal/term-page.c b/src/libsystemd-terminal/term-page.c deleted file mode 100644 index bac85200f1..0000000000 --- a/src/libsystemd-terminal/term-page.c +++ /dev/null @@ -1,2091 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Terminal Page/Line/Cell/Char Handling - * This file implements page handling of a terminal. It is split into pages, - * lines, cells and characters. Each object is independent of the next upper - * object. - * - * The Terminal layer keeps each line of a terminal separate and dynamically - * allocated. This allows us to move lines from main-screen to history-buffers - * very fast. Same is true for scrolling, top/bottom borders and other buffer - * operations. - * - * While lines are dynamically allocated, cells are not. This would be a waste - * of memory and causes heavy fragmentation. Furthermore, cells are moved much - * less frequently than lines so the performance-penalty is pretty small. - * However, to support combining-characters, we have to initialize and cleanup - * cells properly and cannot just release the underlying memory. Therefore, - * cells are treated as proper objects despite being allocated in arrays. - * - * Each cell has a set of attributes and a stored character. This is usually a - * single Unicode character stored as 32bit UCS-4 char. However, we need to - * support Unicode combining-characters, therefore this gets more complicated. - * Characters themselves are represented by a "term_char_t" object. It - * should be treated as a normal integer and passed by value. The - * surrounding struct is just to hide the internals. A term-char can contain a - * base character together with up to 2 combining-chars in a single integer. - * Only if you need more combining-chars (very unlikely!) a term-char is a - * pointer to an allocated storage. This requires you to always free term-char - * objects once no longer used (even though this is a no-op most of the time). - * Furthermore, term-char objects are not ref-counted so you must duplicate them - * in case you want to store it somewhere and retain a copy yourself. By - * convention, all functions that take a term-char object will not duplicate - * it but implicitly take ownership of the passed value. It's up to the caller - * to duplicate it beforehand, in case it wants to retain a copy. - * - * If it turns out, that more than 2 comb-chars become common in specific - * languages, we can try to optimize this. One idea is to ref-count allocated - * characters and store them in a hash-table (like gnome's libvte3 does). This - * way we will never have two allocated chars for the same content. Or we can - * simply put two uint64_t into a "term_char_t". This will slow down operations - * on systems that don't need that many comb-chars, but avoid the dynamic - * allocations on others. - * Anyhow, until we have proper benchmarks, we will keep the current code. It - * seems to compete very well with other solutions so far. - * - * The page-layer is a one-dimensional array of lines. Considering that each - * line is a one-dimensional array of cells, the page layer provides the - * two-dimensional cell-page required for terminals. The page itself only - * operates on lines. All cell-related operations are forwarded to the correct - * line. - * A page does not contain any cursor tracking. It only provides the raw - * operations to shuffle lines and modify the page. - */ - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include "macro.h" -#include "term-internal.h" -#include "util.h" - -/* maximum UCS-4 character */ -#define CHAR_UCS4_MAX (0x10ffff) -/* mask for valid UCS-4 characters (21bit) */ -#define CHAR_UCS4_MASK (0x1fffff) -/* UCS-4 replacement character */ -#define CHAR_UCS4_REPLACEMENT (0xfffd) - -/* real storage behind "term_char_t" in case it's not packed */ -typedef struct term_character { - uint8_t n; - uint32_t codepoints[]; -} term_character; - -/* - * char_pack() takes 3 UCS-4 values and packs them into a term_char_t object. - * Note that UCS-4 chars only take 21 bits, so we still have the LSB as marker. - * We set it to 1 so others can distinguish it from pointers. - */ -static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) { - uint64_t packed, u1, u2, u3; - - u1 = v1; - u2 = v2; - u3 = v3; - - packed = 0x01; - packed |= (u1 & (uint64_t)CHAR_UCS4_MASK) << 43; - packed |= (u2 & (uint64_t)CHAR_UCS4_MASK) << 22; - packed |= (u3 & (uint64_t)CHAR_UCS4_MASK) << 1; - - return TERM_CHAR_INIT(packed); -} - -#define char_pack1(_v1) char_pack2((_v1), CHAR_UCS4_MAX + 1) -#define char_pack2(_v1, _v2) char_pack3((_v1), (_v2), CHAR_UCS4_MAX + 1) -#define char_pack3(_v1, _v2, _v3) char_pack((_v1), (_v2), (_v3)) - -/* - * char_unpack() is the inverse of char_pack(). It extracts the 3 stored UCS-4 - * characters and returns them. Note that this does not validate the passed - * term_char_t. That's the responsibility of the caller. - * This returns the number of characters actually packed. This obviously is a - * number between 0 and 3 (inclusive). - */ -static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) { - uint32_t v1, v2, v3; - - v1 = (packed._value >> 43) & (uint64_t)CHAR_UCS4_MASK; - v2 = (packed._value >> 22) & (uint64_t)CHAR_UCS4_MASK; - v3 = (packed._value >> 1) & (uint64_t)CHAR_UCS4_MASK; - - if (out_v1) - *out_v1 = v1; - if (out_v2) - *out_v2 = v2; - if (out_v3) - *out_v3 = v3; - - return (v1 > CHAR_UCS4_MAX) ? 0 : - ((v2 > CHAR_UCS4_MAX) ? 1 : - ((v3 > CHAR_UCS4_MAX) ? 2 : - 3)); -} - -/* cast a term_char_t to a term_character* */ -static inline term_character *char_to_ptr(term_char_t ch) { - return (term_character*)(unsigned long)ch._value; -} - -/* cast a term_character* to a term_char_t */ -static inline term_char_t char_from_ptr(term_character *c) { - return TERM_CHAR_INIT((unsigned long)c); -} - -/* - * char_alloc() allocates a properly aligned term_character object and returns - * a pointer to it. NULL is returned on allocation errors. The object will have - * enough room for @n following UCS-4 chars. - * Note that we allocate (n+1) characters and set the last one to 0 in case - * anyone prints this string for debugging. - */ -static term_character *char_alloc(uint8_t n) { - term_character *c; - int r; - - r = posix_memalign((void**)&c, - MAX(sizeof(void*), (size_t)2), - sizeof(*c) + sizeof(*c->codepoints) * (n + 1)); - if (r) - return NULL; - - c->n = n; - c->codepoints[n] = 0; - - return c; -} - -/* - * char_free() frees the memory allocated via char_alloc(). It is safe to call - * this on any term_char_t, only allocated characters are freed. - */ -static inline void char_free(term_char_t ch) { - if (term_char_is_allocated(ch)) - free(char_to_ptr(ch)); -} - -/* - * This appends @append_ucs4 to the existing character @base and returns - * it as a new character. In case that's not possible, @base is returned. The - * caller can use term_char_same() to test whether the returned character was - * freshly allocated or not. - */ -static term_char_t char_build(term_char_t base, uint32_t append_ucs4) { - /* soft-limit for combining-chars; hard-limit is currently 255 */ - const size_t climit = 64; - term_character *c; - uint32_t buf[3], *t; - uint8_t n; - - /* ignore invalid UCS-4 */ - if (append_ucs4 > CHAR_UCS4_MAX) - return base; - - if (term_char_is_null(base)) { - return char_pack1(append_ucs4); - } else if (!term_char_is_allocated(base)) { - /* unpack and try extending the packed character */ - n = char_unpack(base, &buf[0], &buf[1], &buf[2]); - - switch (n) { - case 0: - return char_pack1(append_ucs4); - case 1: - if (climit < 2) - return base; - - return char_pack2(buf[0], append_ucs4); - case 2: - if (climit < 3) - return base; - - return char_pack3(buf[0], buf[1], append_ucs4); - default: - /* fallthrough */ - break; - } - - /* already fully packed, we need to allocate a new one */ - t = buf; - } else { - /* already an allocated type, we need to allocate a new one */ - c = char_to_ptr(base); - t = c->codepoints; - n = c->n; - } - - /* bail out if soft-limit is reached */ - if (n >= climit) - return base; - - /* allocate new char */ - c = char_alloc(n + 1); - if (!c) - return base; - - memcpy(c->codepoints, t, sizeof(*t) * n); - c->codepoints[n] = append_ucs4; - - return char_from_ptr(c); -} - -/** - * term_char_set() - Reset character to a single UCS-4 character - * @previous: term-char to reset - * @append_ucs4: UCS-4 char to set - * - * This frees all resources in @previous and re-initializes it to @append_ucs4. - * The new char is returned. - * - * Usually, this is used like this: - * obj->ch = term_char_set(obj->ch, ucs4); - * - * Returns: The previous character reset to @append_ucs4. - */ -term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) { - char_free(previous); - return char_build(TERM_CHAR_NULL, append_ucs4); -} - -/** - * term_char_merge() - Merge UCS-4 char at the end of an existing char - * @base: existing term-char - * @append_ucs4: UCS-4 character to append - * - * This appends @append_ucs4 to @base and returns the result. @base is - * invalidated by this function and must no longer be used. The returned value - * replaces the old one. - * - * Usually, this is used like this: - * obj->ch = term_char_merge(obj->ch, ucs4); - * - * Returns: The new merged character. - */ -term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) { - term_char_t ch; - - ch = char_build(base, append_ucs4); - if (!term_char_same(ch, base)) - term_char_free(base); - - return ch; -} - -/** - * term_char_dup() - Duplicate character - * @ch: character to duplicate - * - * This duplicates a term-character. In case the character is not allocated, - * nothing is done. Otherwise, the underlying memory is copied and returned. You - * need to call term_char_free() on the returned character to release it again. - * On allocation errors, a replacement character is returned. Therefore, the - * caller can safely assume that this function always succeeds. - * - * Returns: The duplicated term-character. - */ -term_char_t term_char_dup(term_char_t ch) { - term_character *c, *newc; - - if (!term_char_is_allocated(ch)) - return ch; - - c = char_to_ptr(ch); - newc = char_alloc(c->n); - if (!newc) - return char_pack1(CHAR_UCS4_REPLACEMENT); - - memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n); - return char_from_ptr(newc); -} - -/** - * term_char_dup_append() - Duplicate tsm-char with UCS-4 character appended - * @base: existing term-char - * @append_ucs4: UCS-4 character to append - * - * This is similar to term_char_merge(), but it returns a separately allocated - * character. That is, @base will stay valid after this returns and is not - * touched. In case the append-operation fails, @base is duplicated and - * returned. That is, the returned char is always independent of @base. - * - * Returns: Newly allocated character with @append_ucs4 appended to @base. - */ -term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) { - term_char_t ch; - - ch = char_build(base, append_ucs4); - if (term_char_same(ch, base)) - ch = term_char_dup(base); - - return ch; -} - -/** - * term_char_resolve() - Retrieve the UCS-4 string for a term-char - * @ch: character to resolve - * @s: storage for size of string or NULL - * @b: storage for string or NULL - * - * This takes a term-character and returns the UCS-4 string associated with it. - * In case @ch is not allocated, the string is stored in @b (in case @b is NULL - * static storage is used). Otherwise, a pointer to the allocated storage is - * returned. - * - * The returned string is only valid as long as @ch and @b are valid. The string - * is zero-terminated and can safely be printed via long-character printf(). - * The length of the string excluding the zero-character is returned in @s. - * - * This never returns NULL. Even if the size is 0, this points to a buffer of at - * least a zero-terminator. - * - * Returns: The UCS-4 string-representation of @ch, and its size in @s. - */ -const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b) { - static term_charbuf_t static_b; - term_character *c; - uint32_t *cache; - size_t len; - - if (b) - cache = b->buf; - else - cache = static_b.buf; - - if (term_char_is_null(ch)) { - len = 0; - cache[0] = 0; - } else if (term_char_is_allocated(ch)) { - c = char_to_ptr(ch); - len = c->n; - cache = c->codepoints; - } else { - len = char_unpack(ch, &cache[0], &cache[1], &cache[2]); - cache[len] = 0; - } - - if (s) - *s = len; - - return cache; -} - -/** - * term_char_lookup_width() - Lookup cell-width of a character - * @ch: character to return cell-width for - * - * This is an equivalent of wcwidth() for term_char_t. It can deal directly - * with UCS-4 and combining-characters and avoids the mess that is wchar_t and - * locale handling. - * - * Returns: 0 for unprintable characters, >0 for everything else. - */ -unsigned int term_char_lookup_width(term_char_t ch) { - term_charbuf_t b; - const uint32_t *str; - unsigned int max; - size_t i, len; - int r; - - max = 0; - str = term_char_resolve(ch, &len, &b); - - for (i = 0; i < len; ++i) { - /* - * Oh god, C99 locale handling strikes again: wcwidth() expects - * wchar_t, but there is no way for us to know the - * internal encoding of wchar_t. Moreover, it is nearly - * impossible to convert UCS-4 into wchar_t (except for iconv, - * which is way too much overhead). - * Therefore, we use our own copy of wcwidth(). Lets just hope - * that glibc will one day export it's internal UCS-4 and UTF-8 - * helpers for direct use. - */ - assert_cc(sizeof(wchar_t) >= 4); - r = mk_wcwidth((wchar_t)str[i]); - if (r > 0 && (unsigned int)r > max) - max = r; - } - - return max; -} - -/** - * term_cell_init() - Initialize a new cell - * @cell: cell to initialize - * @ch: character to set on the cell or TERM_CHAR_NULL - * @cwidth: character width of @ch - * @attr: attributes to set on the cell or NULL - * @age: age to set on the cell or TERM_AGE_NULL - * - * This initializes a new cell. The backing-memory of the cell must be allocated - * by the caller beforehand. The caller is responsible to destroy the cell via - * term_cell_destroy() before freeing the backing-memory. - * - * It is safe (and supported!) to use: - * zero(*c); - * instead of: - * term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL); - * - * Note that this call takes ownership of @ch. If you want to use it yourself - * after this call, you need to duplicate it before calling this. - */ -static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) { - assert(cell); - - cell->ch = ch; - cell->cwidth = cwidth; - cell->age = age; - - if (attr) - memcpy(&cell->attr, attr, sizeof(*attr)); - else - zero(cell->attr); -} - -/** - * term_cell_destroy() - Destroy previously initialized cell - * @cell: cell to destroy or NULL - * - * This releases all resources associated with a cell. The backing memory is - * kept as-is. It's the responsibility of the caller to manage it. - * - * You must not call any other cell operations on this cell after this call - * returns. You must re-initialize the cell via term_cell_init() before you can - * use it again. - * - * If @cell is NULL, this is a no-op. - */ -static void term_cell_destroy(term_cell *cell) { - if (!cell) - return; - - term_char_free(cell->ch); -} - -/** - * term_cell_set() - Change contents of a cell - * @cell: cell to modify - * @ch: character to set on the cell or cell->ch - * @cwidth: character width of @ch or cell->cwidth - * @attr: attributes to set on the cell or NULL - * @age: age to set on the cell or cell->age - * - * This changes the contents of a cell. It can be used to change the character, - * attributes and age. To keep the current character, pass cell->ch as @ch. To - * reset the current attributes, pass NULL. To keep the current age, pass - * cell->age. - * - * This call takes ownership of @ch. You need to duplicate it first, in case you - * want to use it for your own purposes after this call. - * - * The cell must have been initialized properly before calling this. See - * term_cell_init(). - */ -static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) { - assert(cell); - - if (!term_char_same(ch, cell->ch)) { - term_char_free(cell->ch); - cell->ch = ch; - } - - cell->cwidth = cwidth; - cell->age = age; - - if (attr) - memcpy(&cell->attr, attr, sizeof(*attr)); - else - zero(cell->attr); -} - -/** - * term_cell_append() - Append a combining-char to a cell - * @cell: cell to modify - * @ucs4: UCS-4 character to append to the cell - * @age: new age to set on the cell or cell->age - * - * This appends a combining-character to a cell. No validation of the UCS-4 - * character is done, so this can be used to append any character. Additionally, - * this can update the age of the cell. - * - * The cell must have been initialized properly before calling this. See - * term_cell_init(). - */ -static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) { - assert(cell); - - cell->ch = term_char_merge(cell->ch, ucs4); - cell->age = age; -} - -/** - * term_cell_init_n() - Initialize an array of cells - * @cells: pointer to an array of cells to initialize - * @n: number of cells - * @attr: attributes to set on all cells or NULL - * @age: age to set on all cells - * - * This is the same as term_cell_init() but initializes an array of cells. - * Furthermore, this always sets the character to TERM_CHAR_NULL. - * If you want to set a specific characters on all cells, you need to hard-code - * this loop and duplicate the character for each cell. - */ -static void term_cell_init_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) { - for ( ; n > 0; --n, ++cells) - term_cell_init(cells, TERM_CHAR_NULL, 0, attr, age); -} - -/** - * term_cell_destroy_n() - Destroy an array of cells - * @cells: pointer to an array of cells to destroy - * @n: number of cells - * - * This is the same as term_cell_destroy() but destroys an array of cells. - */ -static void term_cell_destroy_n(term_cell *cells, unsigned int n) { - for ( ; n > 0; --n, ++cells) - term_cell_destroy(cells); -} - -/** - * term_cell_clear_n() - Clear contents of an array of cells - * @cells: pointer to an array of cells to modify - * @n: number of cells - * @attr: attributes to set on all cells or NULL - * @age: age to set on all cells - * - * This is the same as term_cell_set() but operates on an array of cells. Note - * that all characters are always set to TERM_CHAR_NULL, unlike term_cell_set() - * which takes the character as argument. - * If you want to set a specific characters on all cells, you need to hard-code - * this loop and duplicate the character for each cell. - */ -static void term_cell_clear_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) { - for ( ; n > 0; --n, ++cells) - term_cell_set(cells, TERM_CHAR_NULL, 0, attr, age); -} - -/** - * term_line_new() - Allocate a new line - * @out: place to store pointer to new line - * - * This allocates and initialized a new line. The line is unlinked and - * independent of any page. It can be used for any purpose. The initial - * cell-count is set to 0. - * - * The line has to be freed via term_line_free() once it's no longer needed. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_line_new(term_line **out) { - _term_line_free_ term_line *line = NULL; - - assert_return(out, -EINVAL); - - line = new0(term_line, 1); - if (!line) - return -ENOMEM; - - *out = line; - line = NULL; - return 0; -} - -/** - * term_line_free() - Free a line - * @line: line to free or NULL - * - * This frees a line that was previously allocated via term_line_free(). All its - * cells are released, too. - * - * If @line is NULL, this is a no-op. - */ -term_line *term_line_free(term_line *line) { - if (!line) - return NULL; - - term_cell_destroy_n(line->cells, line->n_cells); - free(line->cells); - free(line); - - return NULL; -} - -/** - * term_line_reserve() - Pre-allocate cells for a line - * @line: line to pre-allocate cells for - * @width: numbers of cells the line shall have pre-allocated - * @attr: attribute for all allocated cells or NULL - * @age: current age for all modifications - * @protect_width: width to protect from erasure - * - * This pre-allocates cells for this line. Please note that @width is the number - * of cells the line is guaranteed to have allocated after this call returns. - * It's not the number of cells that are added, neither is it the new width of - * the line. - * - * This function never frees memory. That is, reducing the line-width will - * always succeed, same is true for increasing the width to a previously set - * width. - * - * @attr and @age are used to initialize new cells. Additionally, any - * existing cell outside of the protected area specified by @protect_width are - * cleared and reset with @attr and @age. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width) { - unsigned int min_width; - term_cell *t; - - assert_return(line, -EINVAL); - - /* reset existing cells if required */ - min_width = MIN(line->n_cells, width); - if (min_width > protect_width) - term_cell_clear_n(line->cells + protect_width, - min_width - protect_width, - attr, - age); - - /* allocate new cells if required */ - - if (width > line->n_cells) { - t = realloc_multiply(line->cells, sizeof(*t), width); - if (!t) - return -ENOMEM; - - if (!attr && !age) - memzero(t + line->n_cells, - sizeof(*t) * (width - line->n_cells)); - else - term_cell_init_n(t + line->n_cells, - width - line->n_cells, - attr, - age); - - line->cells = t; - line->n_cells = width; - } - - line->fill = MIN(line->fill, protect_width); - - return 0; -} - -/** - * term_line_set_width() - Change width of a line - * @line: line to modify - * @width: new width - * - * This changes the actual width of a line. It is the caller's responsibility - * to use term_line_reserve() to make sure enough space is allocated. If @width - * is greater than the allocated size, it is cropped. - * - * This does not modify any cells. Use term_line_reserve() or term_line_erase() - * to clear any newly added cells. - * - * NOTE: The fill state is cropped at line->width. Therefore, if you increase - * the line-width afterwards, but there is a multi-cell character at the - * end of the line that got cropped, then the fill-state will _not_ be - * adjusted. - * This means, the fill-state always includes the cells up to the start - * of the right-most character, but it might or might not cover it until - * its end. This should be totally fine, though. You should never access - * multi-cell tails directly, anyway. - */ -void term_line_set_width(term_line *line, unsigned int width) { - assert(line); - - if (width > line->n_cells) - width = line->n_cells; - - line->width = width; - line->fill = MIN(line->fill, width); -} - -/** - * line_insert() - Insert characters and move existing cells to the right - * @from: position to insert cells at - * @num: number of cells to insert - * @head_char: character that is set on the first cell - * @head_cwidth: character-length of @head_char - * @attr: attribute for all inserted cells or NULL - * @age: current age for all modifications - * - * The INSERT operation (or writes with INSERT_MODE) writes data at a specific - * position on a line and shifts the existing cells to the right. Cells that are - * moved beyond the right hand border are discarded. - * - * This helper contains the actual INSERT implementation which is independent of - * the data written. It works on cells, not on characters. The first cell is set - * to @head_char, all others are reset to TERM_CHAR_NULL. See each caller for a - * more detailed description. - */ -static inline void line_insert(term_line *line, unsigned int from, unsigned int num, term_char_t head_char, unsigned int head_cwidth, const term_attr *attr, term_age_t age) { - unsigned int i, rem, move; - - if (from >= line->width) - return; - if (from + num < from || from + num > line->width) - num = line->width - from; - if (!num) - return; - - move = line->width - from - num; - rem = MIN(num, move); - - if (rem > 0) { - /* - * Make room for @num cells; shift cells to the right if - * required. @rem is the number of remaining cells that we will - * knock off on the right and overwrite during the right shift. - * - * For INSERT_MODE, @num/@rem are usually 1 or 2, @move is 50% - * of the line on average. Therefore, the actual move is quite - * heavy and we can safely invalidate cells manually instead of - * the whole line. - * However, for INSERT operations, any parameters are - * possible. But we cannot place any assumption on its usage - * across applications, so we just handle it the same as - * INSERT_MODE and do per-cell invalidation. - */ - - /* destroy cells that are knocked off on the right */ - term_cell_destroy_n(line->cells + line->width - rem, rem); - - /* move remaining bulk of cells */ - memmove(line->cells + from + num, - line->cells + from, - sizeof(*line->cells) * move); - - /* invalidate cells */ - for (i = 0; i < move; ++i) - line->cells[from + num + i].age = age; - - /* initialize fresh head-cell */ - term_cell_init(line->cells + from, - head_char, - head_cwidth, - attr, - age); - - /* initialize fresh tail-cells */ - term_cell_init_n(line->cells + from + 1, - num - 1, - attr, - age); - - /* adjust fill-state */ - line->fill = MIN(line->width, - MAX(line->fill + num, - from + num)); - } else { - /* modify head-cell */ - term_cell_set(line->cells + from, - head_char, - head_cwidth, - attr, - age); - - /* reset tail-cells */ - term_cell_clear_n(line->cells + from + 1, - num - 1, - attr, - age); - - /* adjust fill-state */ - line->fill = line->width; - } -} - -/** - * term_line_write() - Write to a single, specific cell - * @line: line to write to - * @pos_x: x-position of cell in @line to write to - * @ch: character to write to the cell - * @cwidth: character width of @ch - * @attr: attributes to set on the cell or NULL - * @age: current age for all modifications - * @insert_mode: true if INSERT-MODE is enabled - * - * This writes to a specific cell in a line. The cell is addressed by its - * X-position @pos_x. If that cell does not exist, this is a no-op. - * - * @ch and @attr are set on this cell. - * - * If @insert_mode is true, this inserts the character instead of overwriting - * existing data (existing data is now moved to the right before writing). - * - * This function is the low-level handler of normal writes to a terminal. - */ -void term_line_write(term_line *line, unsigned int pos_x, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) { - unsigned int len; - - assert(line); - - if (pos_x >= line->width) - return; - - len = MAX(1U, cwidth); - if (pos_x + len < pos_x || pos_x + len > line->width) - len = line->width - pos_x; - if (!len) - return; - - if (insert_mode) { - /* Use line_insert() to insert the character-head and fill - * the remains with NULLs. */ - line_insert(line, pos_x, len, ch, cwidth, attr, age); - } else { - /* modify head-cell */ - term_cell_set(line->cells + pos_x, ch, cwidth, attr, age); - - /* reset tail-cells */ - term_cell_clear_n(line->cells + pos_x + 1, - len - 1, - attr, - age); - - /* adjust fill-state */ - line->fill = MIN(line->width, - MAX(line->fill, - pos_x + len)); - } -} - -/** - * term_line_insert() - Insert empty cells - * @line: line to insert empty cells into - * @from: x-position where to insert cells - * @num: number of cells to insert - * @attr: attributes to set on the cells or NULL - * @age: current age for all modifications - * - * This inserts @num empty cells at position @from in line @line. All existing - * cells to the right are shifted to make room for the new cells. Cells that get - * pushed beyond the right hand border are discarded. - */ -void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) { - /* use line_insert() to insert @num empty cells */ - return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age); -} - -/** - * term_line_delete() - Delete cells from line - * @line: line to delete cells from - * @from: position to delete cells at - * @num: number of cells to delete - * @attr: attributes to set on any new cells - * @age: current age for all modifications - * - * Delete cells from a line. All cells to the right of the deleted cells are - * shifted to the left to fill the empty space. New cells appearing on the right - * hand border are cleared and initialized with @attr. - */ -void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) { - unsigned int rem, move, i; - - assert(line); - - if (from >= line->width) - return; - if (from + num < from || from + num > line->width) - num = line->width - from; - if (!num) - return; - - /* destroy and move as many upfront as possible */ - move = line->width - from - num; - rem = MIN(num, move); - if (rem > 0) { - /* destroy to be removed cells */ - term_cell_destroy_n(line->cells + from, rem); - - /* move tail upfront */ - memmove(line->cells + from, - line->cells + from + num, - sizeof(*line->cells) * move); - - /* invalidate copied cells */ - for (i = 0; i < move; ++i) - line->cells[from + i].age = age; - - /* initialize tail that was moved away */ - term_cell_init_n(line->cells + line->width - rem, - rem, - attr, - age); - - /* reset remaining cells in case the move was too small */ - if (num > move) - term_cell_clear_n(line->cells + from + move, - num - move, - attr, - age); - } else { - /* reset cells */ - term_cell_clear_n(line->cells + from, - num, - attr, - age); - } - - /* adjust fill-state */ - if (from + num < line->fill) - line->fill -= num; - else if (from < line->fill) - line->fill = from; -} - -/** - * term_line_append_combchar() - Append combining char to existing cell - * @line: line to modify - * @pos_x: position of cell to append combining char to - * @ucs4: combining character to append - * @age: current age for all modifications - * - * Unicode allows trailing combining characters, which belong to the - * char in front of them. The caller is responsible of detecting - * combining characters and calling term_line_append_combchar() instead of - * term_line_write(). This simply appends the char to the correct cell then. - * If the cell is not in the visible area, this call is skipped. - * - * Note that control-sequences are not 100% compatible with combining - * characters as they require delayed parsing. However, we must handle - * control-sequences immediately. Therefore, there might be trailing - * combining chars that should be discarded by the parser. - * However, to prevent programming errors, we're also being pedantic - * here and discard weirdly placed combining chars. This prevents - * situations were invalid content is parsed into the terminal and you - * might end up with cells containing only combining chars. - * - * Long story short: To get combining-characters working with old-fashioned - * terminal-emulation, we parse them exclusively for direct cell-writes. Other - * combining-characters are usually simply discarded and ignored. - */ -void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) { - assert(line); - - if (pos_x >= line->width) - return; - - /* Unused cell? Skip appending any combining chars then. */ - if (term_char_is_null(line->cells[pos_x].ch)) - return; - - term_cell_append(line->cells + pos_x, ucs4, age); -} - -/** - * term_line_erase() - Erase parts of a line - * @line: line to modify - * @from: position to start the erase - * @num: number of cells to erase - * @attr: attributes to initialize erased cells with - * @age: current age for all modifications - * @keep_protected: true if protected cells should be kept - * - * This is the standard erase operation. It clears all cells in the targeted - * area and re-initializes them. Cells to the right are not shifted left, you - * must use DELETE to achieve that. Cells outside the visible area are skipped. - * - * If @keep_protected is true, protected cells will not be erased. - */ -void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) { - term_cell *cell; - unsigned int i, last_protected; - - assert(line); - - if (from >= line->width) - return; - if (from + num < from || from + num > line->width) - num = line->width - from; - if (!num) - return; - - last_protected = 0; - for (i = 0; i < num; ++i) { - cell = line->cells + from + i; - if (keep_protected && cell->attr.protect) { - /* only count protected-cells inside the fill-region */ - if (from + i < line->fill) - last_protected = from + i; - - continue; - } - - term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age); - } - - /* Adjust fill-state. This is a bit tricks, we can only adjust it in - * case the erase-region starts inside the fill-region and ends at the - * tail or beyond the fill-region. Otherwise, the current fill-state - * stays as it was. - * Furthermore, we must account for protected cells. The loop above - * ensures that protected-cells are only accounted for if they're - * inside the fill-region. */ - if (from < line->fill && from + num >= line->fill) - line->fill = MAX(from, last_protected); -} - -/** - * term_line_reset() - Reset a line - * @line: line to reset - * @attr: attributes to initialize all cells with - * @age: current age for all modifications - * - * This resets all visible cells of a line and sets their attributes and ages - * to @attr and @age. This is equivalent to erasing a whole line via - * term_line_erase(). - */ -void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) { - assert(line); - - return term_line_erase(line, 0, line->width, attr, age, 0); -} - -/** - * term_line_link() - Link line in front of a list - * @line: line to link - * @first: member pointing to first entry - * @last: member pointing to last entry - * - * This links a line into a list of lines. The line is inserted at the front and - * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of - * this. - */ -void term_line_link(term_line *line, term_line **first, term_line **last) { - assert(line); - assert(first); - assert(last); - assert(!line->lines_prev); - assert(!line->lines_next); - - line->lines_prev = NULL; - line->lines_next = *first; - if (*first) - (*first)->lines_prev = line; - else - *last = line; - *first = line; -} - -/** - * term_line_link_tail() - Link line at tail of a list - * @line: line to link - * @first: member pointing to first entry - * @last: member pointing to last entry - * - * Same as term_line_link() but links the line at the tail. - */ -void term_line_link_tail(term_line *line, term_line **first, term_line **last) { - assert(line); - assert(first); - assert(last); - assert(!line->lines_prev); - assert(!line->lines_next); - - line->lines_next = NULL; - line->lines_prev = *last; - if (*last) - (*last)->lines_next = line; - else - *first = line; - *last = line; -} - -/** - * term_line_unlink() - Unlink line from a list - * @line: line to unlink - * @first: member pointing to first entry - * @last: member pointing to last entry - * - * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to - * use macro. - */ -void term_line_unlink(term_line *line, term_line **first, term_line **last) { - assert(line); - assert(first); - assert(last); - - if (line->lines_prev) - line->lines_prev->lines_next = line->lines_next; - else - *first = line->lines_next; - if (line->lines_next) - line->lines_next->lines_prev = line->lines_prev; - else - *last = line->lines_prev; - - line->lines_prev = NULL; - line->lines_next = NULL; -} - -/** - * term_page_new() - Allocate new page - * @out: storage for pointer to new page - * - * Allocate a new page. The initial dimensions are 0/0. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_page_new(term_page **out) { - _term_page_free_ term_page *page = NULL; - - assert_return(out, -EINVAL); - - page = new0(term_page, 1); - if (!page) - return -ENOMEM; - - *out = page; - page = NULL; - return 0; -} - -/** - * term_page_free() - Free page - * @page: page to free or NULL - * - * Free a previously allocated page and all associated data. If @page is NULL, - * this is a no-op. - * - * Returns: NULL - */ -term_page *term_page_free(term_page *page) { - unsigned int i; - - if (!page) - return NULL; - - for (i = 0; i < page->n_lines; ++i) - term_line_free(page->lines[i]); - - free(page->line_cache); - free(page->lines); - free(page); - - return NULL; -} - -/** - * term_page_get_cell() - Return pointer to requested cell - * @page: page to operate on - * @x: x-position of cell - * @y: y-position of cell - * - * This returns a pointer to the cell at position @x/@y. You're free to modify - * this cell as much as you like. However, once you call any other function on - * the page, you must drop the pointer to the cell. - * - * Returns: Pointer to the cell or NULL if out of the visible area. - */ -term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) { - assert_return(page, NULL); - - if (x >= page->width) - return NULL; - if (y >= page->height) - return NULL; - - return &page->lines[y]->cells[x]; -} - -/** - * page_scroll_up() - Scroll up - * @page: page to operate on - * @new_width: width to use for any new line moved into the visible area - * @num: number of lines to scroll up - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for old lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are cleared and reset - * with the given attributes. Old lines are moved into the history if non-NULL. - * If a new line is allocated, moved from the history buffer or moved from - * outside the visible region into the visible region, this call makes sure it - * has at least @width cells allocated. If a possible memory-allocation fails, - * the previous line is reused. This has the side effect, that it will not be - * linked into the history buffer. - * - * If the scroll-region is empty, this is a no-op. - */ -static void page_scroll_up(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - term_line *line, **cache; - unsigned int i; - int r; - - assert(page); - - if (num > page->scroll_num) - num = page->scroll_num; - if (num < 1) - return; - - /* Better safe than sorry: avoid under-allocating lines, even when - * resizing. */ - new_width = MAX(new_width, page->width); - - cache = page->line_cache; - - /* Try moving lines into history and allocate new lines for each moved - * line. In case allocation fails, or if we have no history, reuse the - * line. - * We keep the lines in the line-cache so we can safely move the - * remaining lines around. */ - for (i = 0; i < num; ++i) { - line = page->lines[page->scroll_idx + i]; - - r = -EAGAIN; - if (history) { - r = term_line_new(&cache[i]); - if (r >= 0) { - r = term_line_reserve(cache[i], - new_width, - attr, - age, - 0); - if (r < 0) - term_line_free(cache[i]); - else - term_line_set_width(cache[i], page->width); - } - } - - if (r >= 0) { - term_history_push(history, line); - } else { - cache[i] = line; - term_line_reset(line, attr, age); - } - } - - if (num < page->scroll_num) { - memmove(page->lines + page->scroll_idx, - page->lines + page->scroll_idx + num, - sizeof(*page->lines) * (page->scroll_num - num)); - - /* update age of moved lines */ - for (i = 0; i < page->scroll_num - num; ++i) - page->lines[page->scroll_idx + i]->age = age; - } - - /* copy remaining lines from cache; age is already updated */ - memcpy(page->lines + page->scroll_idx + page->scroll_num - num, - cache, - sizeof(*cache) * num); - - /* update fill */ - page->scroll_fill -= MIN(page->scroll_fill, num); -} - -/** - * page_scroll_down() - Scroll down - * @page: page to operate on - * @new_width: width to use for any new line moved into the visible area - * @num: number of lines to scroll down - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for new lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are retrieved from - * the history or cleared if the history is empty or NULL. - * - * Usually, scroll-down implies that new lines are cleared. Therefore, you're - * highly encouraged to set @history to NULL. However, if you resize a terminal, - * you might want to include history-lines in the new area. In that case, you - * should set @history to non-NULL. - * - * If a new line is allocated, moved from the history buffer or moved from - * outside the visible region into the visible region, this call makes sure it - * has at least @width cells allocated. If a possible memory-allocation fails, - * the previous line is reused. This will have the side-effect that lines from - * the history will not get visible on-screen but kept in history. - * - * If the scroll-region is empty, this is a no-op. - */ -static void page_scroll_down(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - term_line *line, **cache, *t; - unsigned int i, last_idx; - - assert(page); - - if (num > page->scroll_num) - num = page->scroll_num; - if (num < 1) - return; - - /* Better safe than sorry: avoid under-allocating lines, even when - * resizing. */ - new_width = MAX(new_width, page->width); - - cache = page->line_cache; - last_idx = page->scroll_idx + page->scroll_num - 1; - - /* Try pulling out lines from history; if history is empty or if no - * history is given, we reuse the to-be-removed lines. Otherwise, those - * lines are released. */ - for (i = 0; i < num; ++i) { - line = page->lines[last_idx - i]; - - t = NULL; - if (history) - t = term_history_pop(history, new_width, attr, age); - - if (t) { - cache[num - 1 - i] = t; - term_line_free(line); - } else { - cache[num - 1 - i] = line; - term_line_reset(line, attr, age); - } - } - - if (num < page->scroll_num) { - memmove(page->lines + page->scroll_idx + num, - page->lines + page->scroll_idx, - sizeof(*page->lines) * (page->scroll_num - num)); - - /* update age of moved lines */ - for (i = 0; i < page->scroll_num - num; ++i) - page->lines[page->scroll_idx + num + i]->age = age; - } - - /* copy remaining lines from cache; age is already updated */ - memcpy(page->lines + page->scroll_idx, - cache, - sizeof(*cache) * num); - - /* update fill; but only if there's already content in it */ - if (page->scroll_fill > 0) - page->scroll_fill = MIN(page->scroll_num, - page->scroll_fill + num); -} - -/** - * page_reserve() - Reserve page area - * @page: page to modify - * @cols: required columns (width) - * @rows: required rows (height) - * @attr: attributes for newly allocated cells - * @age: age to set on any modified cells - * - * This allocates the required amount of lines and cells to guarantee that the - * page has at least the demanded dimensions of @cols x @rows. Note that this - * never shrinks the page-memory. We keep cells allocated for performance - * reasons. - * - * Additionally to allocating lines, this also clears any newly added cells so - * you can safely change the size afterwards without clearing new cells. - * - * Note that you must be careful what operations you call on the page between - * page_reserve() and updating page->width/height. Any newly allocated line (or - * shifted line) might not meet your new width/height expectations. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) { - _term_line_free_ term_line *line = NULL; - unsigned int i, min_lines; - term_line **t; - int r; - - assert_return(page, -EINVAL); - - /* - * First make sure the first MIN(page->n_lines, rows) lines have at - * least the required width of @cols. This does not modify any visible - * cells in the existing @page->width x @page->height area, therefore, - * we can safely bail out afterwards in case anything else fails. - * Note that lines in between page->height and page->n_lines might be - * shorter than page->width. Hence, we need to resize them all, but we - * can skip some of them for better performance. - */ - min_lines = MIN(page->n_lines, rows); - for (i = 0; i < min_lines; ++i) { - /* lines below page->height have at least page->width cells */ - if (cols < page->width && i < page->height) - continue; - - r = term_line_reserve(page->lines[i], - cols, - attr, - age, - (i < page->height) ? page->width : 0); - if (r < 0) - return r; - } - - /* - * We now know the first @min_lines lines have at least width @cols and - * are prepared for resizing. We now only have to allocate any - * additional lines below @min_lines in case @rows is greater than - * page->n_lines. - */ - if (rows > page->n_lines) { - t = realloc_multiply(page->lines, sizeof(*t), rows); - if (!t) - return -ENOMEM; - page->lines = t; - - t = realloc_multiply(page->line_cache, sizeof(*t), rows); - if (!t) - return -ENOMEM; - page->line_cache = t; - - while (page->n_lines < rows) { - r = term_line_new(&line); - if (r < 0) - return r; - - r = term_line_reserve(line, cols, attr, age, 0); - if (r < 0) - return r; - - page->lines[page->n_lines++] = line; - line = NULL; - } - } - - return 0; -} - -/** - * term_page_resize() - Resize page - * @page: page to modify - * @cols: number of columns (width) - * @rows: number of rows (height) - * @attr: attributes for newly allocated cells - * @age: age to set on any modified cells - * @history: history buffer to use for new/old lines or NULL - * - * This changes the visible dimensions of a page. You must have called - * term_page_reserve() beforehand, otherwise, this will fail. - * - * Returns: 0 on success, negative error code on failure. - */ -void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) { - unsigned int i, num, empty, max, old_height; - term_line *line; - - assert(page); - assert(page->n_lines >= rows); - - old_height = page->height; - - if (rows < old_height) { - /* - * If we decrease the terminal-height, we emulate a scroll-up. - * This way, existing data from the scroll-area is moved into - * the history, making space at the bottom to reduce the screen - * height. In case the scroll-fill indicates empty lines, we - * reduce the amount of scrolled lines. - * Once scrolled, we have to move the lower margin from below - * the scroll area up so it is preserved. - */ - - /* move lines to history if scroll region is filled */ - num = old_height - rows; - empty = page->scroll_num - page->scroll_fill; - if (num > empty) - page_scroll_up(page, - cols, - num - empty, - attr, - age, - history); - - /* move lower margin up; drop its lines if not enough space */ - num = LESS_BY(old_height, page->scroll_idx + page->scroll_num); - max = LESS_BY(rows, page->scroll_idx); - num = MIN(num, max); - if (num > 0) { - unsigned int top, bottom; - - top = rows - num; - bottom = page->scroll_idx + page->scroll_num; - - /* might overlap; must run topdown, not bottomup */ - for (i = 0; i < num; ++i) { - line = page->lines[top + i]; - page->lines[top + i] = page->lines[bottom + i]; - page->lines[bottom + i] = line; - } - } - - /* update vertical extents */ - page->height = rows; - page->scroll_idx = MIN(page->scroll_idx, rows); - page->scroll_num -= MIN(page->scroll_num, old_height - rows); - /* fill is already up-to-date or 0 due to scroll-up */ - } else if (rows > old_height) { - /* - * If we increase the terminal-height, we emulate a scroll-down - * and fetch new lines from the history. - * New lines are always accounted to the scroll-region. Thus we - * have to preserve the lower margin first, by moving it down. - */ - - /* move lower margin down */ - num = LESS_BY(old_height, page->scroll_idx + page->scroll_num); - if (num > 0) { - unsigned int top, bottom; - - top = page->scroll_idx + page->scroll_num; - bottom = top + (rows - old_height); - - /* might overlap; must run bottomup, not topdown */ - for (i = num; i-- > 0; ) { - line = page->lines[top + i]; - page->lines[top + i] = page->lines[bottom + i]; - page->lines[bottom + i] = line; - } - } - - /* update vertical extents */ - page->height = rows; - page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx), - page->scroll_num + (rows - old_height)); - - /* check how many lines can be received from history */ - if (history) - num = term_history_peek(history, - rows - old_height, - cols, - attr, - age); - else - num = 0; - - /* retrieve new lines from history if available */ - if (num > 0) - page_scroll_down(page, - cols, - num, - attr, - age, - history); - } - - /* set horizontal extents */ - page->width = cols; - for (i = 0; i < page->height; ++i) - term_line_set_width(page->lines[i], cols); -} - -/** - * term_page_write() - Write to a single cell - * @page: page to operate on - * @pos_x: x-position of cell to write to - * @pos_y: y-position of cell to write to - * @ch: character to write - * @cwidth: character-width of @ch - * @attr: attributes to set on the cell or NULL - * @age: age to use for all modifications - * @insert_mode: true if INSERT-MODE is enabled - * - * This writes a character to a specific cell. If the cell is beyond bounds, - * this is a no-op. @attr and @age are used to update the cell. @flags can be - * used to alter the behavior of this function. - * - * This is a wrapper around term_line_write(). - * - * This call does not wrap around lines. That is, this only operates on a single - * line. - */ -void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) { - assert(page); - - if (pos_y >= page->height) - return; - - term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode); -} - -/** - * term_page_insert_cells() - Insert cells into a line - * @page: page to operate on - * @from_x: x-position where to insert new cells - * @from_y: y-position where to insert new cells - * @num: number of cells to insert - * @attr: attributes to set on new cells or NULL - * @age: age to use for all modifications - * - * This inserts new cells into a given line. This is a wrapper around - * term_line_insert(). - * - * This call does not wrap around lines. That is, this only operates on a single - * line. - */ -void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) { - assert(page); - - if (from_y >= page->height) - return; - - term_line_insert(page->lines[from_y], from_x, num, attr, age); -} - -/** - * term_page_delete_cells() - Delete cells from a line - * @page: page to operate on - * @from_x: x-position where to delete cells - * @from_y: y-position where to delete cells - * @num: number of cells to delete - * @attr: attributes to set on new cells or NULL - * @age: age to use for all modifications - * - * This deletes cells from a given line. This is a wrapper around - * term_line_delete(). - * - * This call does not wrap around lines. That is, this only operates on a single - * line. - */ -void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) { - assert(page); - - if (from_y >= page->height) - return; - - term_line_delete(page->lines[from_y], from_x, num, attr, age); -} - -/** - * term_page_append_combchar() - Append combining-character to a cell - * @page: page to operate on - * @pos_x: x-position of target cell - * @pos_y: y-position of target cell - * @ucs4: combining character to append - * @age: age to use for all modifications - * - * This appends a combining-character to a specific cell. This is a wrapper - * around term_line_append_combchar(). - */ -void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) { - assert(page); - - if (pos_y >= page->height) - return; - - term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age); -} - -/** - * term_page_erase() - Erase parts of a page - * @page: page to operate on - * @from_x: x-position where to start erasure (inclusive) - * @from_y: y-position where to start erasure (inclusive) - * @to_x: x-position where to stop erasure (inclusive) - * @to_y: y-position where to stop erasure (inclusive) - * @attr: attributes to set on cells - * @age: age to use for all modifications - * @keep_protected: true if protected cells should be kept - * - * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note - * that this wraps around line-boundaries so lines between @from_y and @to_y - * are cleared entirely. - * - * Lines outside the visible area are left untouched. - */ -void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected) { - unsigned int i, from, to; - - assert(page); - - for (i = from_y; i <= to_y && i < page->height; ++i) { - from = 0; - to = page->width; - - if (i == from_y) - from = from_x; - if (i == to_y) - to = to_x; - - term_line_erase(page->lines[i], - from, - LESS_BY(to, from), - attr, - age, - keep_protected); - } -} - -/** - * term_page_reset() - Reset page - * @page: page to modify - * @attr: attributes to set on cells - * @age: age to use for all modifications - * - * This erases the whole visible page. See term_page_erase(). - */ -void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) { - assert(page); - - return term_page_erase(page, - 0, 0, - page->width - 1, page->height - 1, - attr, - age, - 0); -} - -/** - * term_page_set_scroll_region() - Set scroll region - * @page: page to operate on - * @idx: start-index of scroll region - * @num: number of lines in scroll region - * - * This sets the scroll region of a page. Whenever an operation needs to scroll - * lines, it scrolls them inside of that region. Lines outside the region are - * left untouched. In case a scroll-operation is targeted outside of this - * region, it will implicitly get a scroll-region of only one line (i.e., no - * scroll region at all). - * - * Note that the scroll-region is clipped to the current page-extents. Growing - * or shrinking the page always accounts new/old lines to the scroll region and - * moves top/bottom margins accordingly so they're preserved. - */ -void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) { - assert(page); - - if (page->height < 1) { - page->scroll_idx = 0; - page->scroll_num = 0; - } else { - page->scroll_idx = MIN(idx, page->height - 1); - page->scroll_num = MIN(num, page->height - page->scroll_idx); - } -} - -/** - * term_page_scroll_up() - Scroll up - * @page: page to operate on - * @num: number of lines to scroll up - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for old lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are cleared and reset - * with the given attributes. Old lines are moved into the history if non-NULL. - * - * If the scroll-region is empty, this is a no-op. - */ -void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - page_scroll_up(page, page->width, num, attr, age, history); -} - -/** - * term_page_scroll_down() - Scroll down - * @page: page to operate on - * @num: number of lines to scroll down - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for new lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are retrieved from - * the history or cleared if the history is empty or NULL. - * - * Usually, scroll-down implies that new lines are cleared. Therefore, you're - * highly encouraged to set @history to NULL. However, if you resize a terminal, - * you might want to include history-lines in the new area. In that case, you - * should set @history to non-NULL. - * - * If the scroll-region is empty, this is a no-op. - */ -void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - page_scroll_down(page, page->width, num, attr, age, history); -} - -/** - * term_page_insert_lines() - Insert new lines - * @page: page to operate on - * @pos_y: y-position where to insert new lines - * @num: number of lines to insert - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * - * This inserts @num new lines at position @pos_y. If @pos_y is beyond - * boundaries or @num is 0, this is a no-op. - * All lines below @pos_y are moved down to make space for the new lines. Lines - * on the bottom are dropped. Note that this only moves lines above or inside - * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of - * one line is implied (which means the line is simply cleared). - */ -void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) { - unsigned int scroll_idx, scroll_num; - - assert(page); - - if (pos_y >= page->height) - return; - if (num >= page->height) - num = page->height; - - /* remember scroll-region */ - scroll_idx = page->scroll_idx; - scroll_num = page->scroll_num; - - /* set scroll-region temporarily so we can reuse scroll_down() */ - { - page->scroll_idx = pos_y; - if (pos_y >= scroll_idx + scroll_num) - page->scroll_num = 1; - else if (pos_y >= scroll_idx) - page->scroll_num -= pos_y - scroll_idx; - else - page->scroll_num += scroll_idx - pos_y; - - term_page_scroll_down(page, num, attr, age, NULL); - } - - /* reset scroll-region */ - page->scroll_idx = scroll_idx; - page->scroll_num = scroll_num; -} - -/** - * term_page_delete_lines() - Delete lines - * @page: page to operate on - * @pos_y: y-position where to delete lines - * @num: number of lines to delete - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * - * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or - * @num is 0, this is a no-op. - * All lines below @pos_y are moved up into the newly made space. New lines - * on the bottom are clear. Note that this only moves lines above or inside - * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of - * one line is implied (which means the line is simply cleared). - */ -void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) { - unsigned int scroll_idx, scroll_num; - - assert(page); - - if (pos_y >= page->height) - return; - if (num >= page->height) - num = page->height; - - /* remember scroll-region */ - scroll_idx = page->scroll_idx; - scroll_num = page->scroll_num; - - /* set scroll-region temporarily so we can reuse scroll_up() */ - { - page->scroll_idx = pos_y; - if (pos_y >= scroll_idx + scroll_num) - page->scroll_num = 1; - else if (pos_y > scroll_idx) - page->scroll_num -= pos_y - scroll_idx; - else - page->scroll_num += scroll_idx - pos_y; - - term_page_scroll_up(page, num, attr, age, NULL); - } - - /* reset scroll-region */ - page->scroll_idx = scroll_idx; - page->scroll_num = scroll_num; -} - -/** - * term_history_new() - Create new history object - * @out: storage for pointer to new history - * - * Create a new history object. Histories are used to store scrollback-lines - * from VTE pages. You're highly recommended to set a history-limit on - * history->max_lines and trim it via term_history_trim(), otherwise history - * allocations are unlimited. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_history_new(term_history **out) { - _term_history_free_ term_history *history = NULL; - - assert_return(out, -EINVAL); - - history = new0(term_history, 1); - if (!history) - return -ENOMEM; - - history->max_lines = 4096; - - *out = history; - history = NULL; - return 0; -} - -/** - * term_history_free() - Free history - * @history: history to free - * - * Clear and free history. You must not access the object afterwards. - * - * Returns: NULL - */ -term_history *term_history_free(term_history *history) { - if (!history) - return NULL; - - term_history_clear(history); - free(history); - return NULL; -} - -/** - * term_history_clear() - Clear history - * @history: history to clear - * - * Remove all linked lines from a history and reset it to its initial state. - */ -void term_history_clear(term_history *history) { - return term_history_trim(history, 0); -} - -/** - * term_history_trim() - Trim history - * @history: history to trim - * @max: maximum number of lines to be left in history - * - * This removes lines from the history until it is smaller than @max. Lines are - * removed from the top. - */ -void term_history_trim(term_history *history, unsigned int max) { - term_line *line; - - if (!history) - return; - - while (history->n_lines > max && (line = history->lines_first)) { - TERM_LINE_UNLINK(line, history); - term_line_free(line); - --history->n_lines; - } -} - -/** - * term_history_push() - Push line into history - * @history: history to work on - * @line: line to push into history - * - * This pushes a line into the given history. It is linked at the tail. In case - * the history is limited, the top-most line might be freed. - */ -void term_history_push(term_history *history, term_line *line) { - assert(history); - assert(line); - - TERM_LINE_LINK_TAIL(line, history); - if (history->max_lines > 0 && history->n_lines >= history->max_lines) { - line = history->lines_first; - TERM_LINE_UNLINK(line, history); - term_line_free(line); - } else { - ++history->n_lines; - } -} - -/** - * term_history_pop() - Retrieve last line from history - * @history: history to work on - * @new_width: width to reserve and set on the line - * @attr: attributes to use for cell reservation - * @age: age to use for cell reservation - * - * This unlinks the last linked line of the history and returns it. This also - * makes sure the line has the given width pre-allocated (see - * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it - * is treated like there's no line in history left. This simplifies - * history-handling on the caller's side in case of allocation errors. No need - * to throw lines away just because the reservation failed. We can keep them in - * history safely, and make them available as scrollback. - * - * Returns: Line from history or NULL - */ -term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) { - term_line *line; - int r; - - assert_return(history, NULL); - - line = history->lines_last; - if (!line) - return NULL; - - r = term_line_reserve(line, new_width, attr, age, line->width); - if (r < 0) - return NULL; - - term_line_set_width(line, new_width); - TERM_LINE_UNLINK(line, history); - --history->n_lines; - - return line; -} - -/** - * term_history_peek() - Return number of available history-lines - * @history: history to work on - * @max: maximum number of lines to look at - * @reserve_width: width to reserve on the line - * @attr: attributes to use for cell reservation - * @age: age to use for cell reservation - * - * This returns the number of available lines in the history given as @history. - * It returns at most @max. For each line that is looked at, the line is - * verified to have at least @reserve_width cells. Valid cells are preserved, - * new cells are initialized with @attr and @age. In case an allocation fails, - * we bail out and return the number of lines that are valid so far. - * - * Usually, this function should be used before running a loop on - * term_history_pop(). This function guarantees that term_history_pop() (with - * the same arguments) will succeed at least the returned number of times. - * - * Returns: Number of valid lines that can be received via term_history_pop(). - */ -unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) { - unsigned int num; - term_line *line; - int r; - - assert(history); - - num = 0; - line = history->lines_last; - - while (num < max && line) { - r = term_line_reserve(line, reserve_width, attr, age, line->width); - if (r < 0) - break; - - ++num; - line = line->lines_prev; - } - - return num; -} diff --git a/src/libsystemd-terminal/term-parser.c b/src/libsystemd-terminal/term-parser.c deleted file mode 100644 index 8dc1da2f9c..0000000000 --- a/src/libsystemd-terminal/term-parser.c +++ /dev/null @@ -1,1702 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Terminal Parser - * This file contains a bunch of UTF-8 helpers and the main ctlseq-parser. The - * parser is a simple state-machine that correctly parses all CSI, DCS, OSC, ST - * control sequences and generic escape sequences. - * The parser itself does not perform any actions but lets the caller react to - * detected sequences. - */ - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include "macro.h" -#include "term-internal.h" -#include "util.h" - -static const uint8_t default_palette[18][3] = { - { 0, 0, 0 }, /* black */ - { 205, 0, 0 }, /* red */ - { 0, 205, 0 }, /* green */ - { 205, 205, 0 }, /* yellow */ - { 0, 0, 238 }, /* blue */ - { 205, 0, 205 }, /* magenta */ - { 0, 205, 205 }, /* cyan */ - { 229, 229, 229 }, /* light grey */ - { 127, 127, 127 }, /* dark grey */ - { 255, 0, 0 }, /* light red */ - { 0, 255, 0 }, /* light green */ - { 255, 255, 0 }, /* light yellow */ - { 92, 92, 255 }, /* light blue */ - { 255, 0, 255 }, /* light magenta */ - { 0, 255, 255 }, /* light cyan */ - { 255, 255, 255 }, /* white */ - - { 229, 229, 229 }, /* light grey */ - { 0, 0, 0 }, /* black */ -}; - -static uint32_t term_color_to_argb32(const term_color *color, const term_attr *attr, const uint8_t *palette) { - static const uint8_t bval[] = { - 0x00, 0x5f, 0x87, - 0xaf, 0xd7, 0xff, - }; - uint8_t r, g, b, t; - - assert(color); - - if (!palette) - palette = (void*)default_palette; - - switch (color->ccode) { - case TERM_CCODE_RGB: - r = color->red; - g = color->green; - b = color->blue; - - break; - case TERM_CCODE_256: - t = color->c256; - if (t < 16) { - r = palette[t * 3 + 0]; - g = palette[t * 3 + 1]; - b = palette[t * 3 + 2]; - } else if (t < 232) { - t -= 16; - b = bval[t % 6]; - t /= 6; - g = bval[t % 6]; - t /= 6; - r = bval[t % 6]; - } else { - t = (t - 232) * 10 + 8; - r = t; - g = t; - b = t; - } - - break; - case TERM_CCODE_BLACK ... TERM_CCODE_LIGHT_WHITE: - t = color->ccode - TERM_CCODE_BLACK; - - /* bold causes light colors (only for foreground colors) */ - if (t < 8 && attr->bold && color == &attr->fg) - t += 8; - - r = palette[t * 3 + 0]; - g = palette[t * 3 + 1]; - b = palette[t * 3 + 2]; - break; - case TERM_CCODE_DEFAULT: - /* fallthrough */ - default: - t = 16 + !(color == &attr->fg); - r = palette[t * 3 + 0]; - g = palette[t * 3 + 1]; - b = palette[t * 3 + 2]; - break; - } - - return (0xff << 24) | (r << 16) | (g << 8) | b; -} - -/** - * term_attr_to_argb32() - Encode terminal colors as native ARGB32 value - * @color: Terminal attributes to work on - * @fg: Storage for foreground color (or NULL) - * @bg: Storage for background color (or NULL) - * @palette: The color palette to use (or NULL for default) - * - * This encodes the colors attr->fg and attr->bg as native-endian ARGB32 values - * and returns them. Any color conversions are automatically applied. - */ -void term_attr_to_argb32(const term_attr *attr, uint32_t *fg, uint32_t *bg, const uint8_t *palette) { - uint32_t f, b, t; - - assert(attr); - - f = term_color_to_argb32(&attr->fg, attr, palette); - b = term_color_to_argb32(&attr->bg, attr, palette); - - if (attr->inverse) { - t = f; - f = b; - b = t; - } - - if (fg) - *fg = f; - if (bg) - *bg = b; -} - -/** - * term_utf8_decode() - Try decoding the next UCS-4 character - * @p: decoder object to operate on or NULL - * @out_len: output storage for pointer to decoded UCS-4 string or NULL - * @c: next char to push into decoder - * - * This decodes a UTF-8 stream. It must be called for each input-byte of the - * UTF-8 stream and returns a UCS-4 stream. A pointer to the parsed UCS-4 - * string is stored in @out_buf if non-NULL. The length of this string (number - * of parsed UCS4 characters) is returned as result. The string is not - * zero-terminated! Furthermore, the string is only valid until the next - * invocation of this function. It is also bound to the parser state @p and - * must not be freed nor written to by the caller. - * - * This function is highly optimized to work with terminal-emulators. Instead - * of being strict about UTF-8 validity, this tries to perform a fallback to - * ISO-8859-1 in case a wrong series was detected. Therefore, this function - * might return multiple UCS-4 characters by parsing just a single UTF-8 byte. - * - * The parser state @p should be allocated and managed by the caller. There're - * no helpers to do that for you. To initialize it, simply reset it to all - * zero. You can reset or free the object at any point in time. - * - * Returns: Number of parsed UCS4 characters - */ -size_t term_utf8_decode(term_utf8 *p, uint32_t **out_buf, char c) { - static uint32_t ucs4_null = 0; - uint32_t t, *res = NULL; - uint8_t byte; - size_t len = 0; - - if (!p) - goto out; - - byte = c; - - if (!p->valid || p->i_bytes >= p->n_bytes) { - /* - * If the previous sequence was invalid or fully parsed, start - * parsing a fresh new sequence. - */ - - if ((byte & 0xE0) == 0xC0) { - /* start of two byte sequence */ - t = byte & 0x1F; - p->n_bytes = 2; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF0) == 0xE0) { - /* start of three byte sequence */ - t = byte & 0x0F; - p->n_bytes = 3; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF8) == 0xF0) { - /* start of four byte sequence */ - t = byte & 0x07; - p->n_bytes = 4; - p->i_bytes = 1; - p->valid = 1; - } else { - /* Either of: - * - single ASCII 7-bit char - * - out-of-sync continuation byte - * - overlong encoding - * All of them are treated as single byte ISO-8859-1 */ - t = byte; - p->n_bytes = 1; - p->i_bytes = 1; - p->valid = 0; - } - - p->chars[0] = byte; - p->ucs4 = t << (6 * (p->n_bytes - p->i_bytes)); - } else { - /* - * ..otherwise, try to continue the previous sequence.. - */ - - if ((byte & 0xC0) == 0x80) { - /* - * Valid continuation byte. Append to sequence and - * update the ucs4 cache accordingly. - */ - - t = byte & 0x3F; - p->chars[p->i_bytes++] = byte; - p->ucs4 |= t << (6 * (p->n_bytes - p->i_bytes)); - } else { - /* - * Invalid continuation? Treat cached sequence as - * ISO-8859-1, but parse the new char as valid new - * starting character. If it's a new single-byte UTF-8 - * sequence, we immediately return it in the same run, - * otherwise, we might suffer from starvation. - */ - - if ((byte & 0xE0) == 0xC0 || - (byte & 0xF0) == 0xE0 || - (byte & 0xF8) == 0xF0) { - /* - * New multi-byte sequence. Move to-be-returned - * data at the end and start new sequence. Only - * return the old sequence. - */ - - memmove(p->chars + 1, - p->chars, - sizeof(*p->chars) * p->i_bytes); - res = p->chars + 1; - len = p->i_bytes; - - if ((byte & 0xE0) == 0xC0) { - /* start of two byte sequence */ - t = byte & 0x1F; - p->n_bytes = 2; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF0) == 0xE0) { - /* start of three byte sequence */ - t = byte & 0x0F; - p->n_bytes = 3; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF8) == 0xF0) { - /* start of four byte sequence */ - t = byte & 0x07; - p->n_bytes = 4; - p->i_bytes = 1; - p->valid = 1; - } else - assert_not_reached("Should not happen"); - - p->chars[0] = byte; - p->ucs4 = t << (6 * (p->n_bytes - p->i_bytes)); - - goto out; - } else { - /* - * New single byte sequence, append to output - * and return combined sequence. - */ - - p->chars[p->i_bytes++] = byte; - p->valid = 0; - } - } - } - - /* - * Check whether a full sequence (valid or invalid) has been parsed and - * then return it. Otherwise, return nothing. - */ - if (p->valid) { - /* still parsing? then bail out */ - if (p->i_bytes < p->n_bytes) - goto out; - - res = &p->ucs4; - len = 1; - } else { - res = p->chars; - len = p->i_bytes; - } - - p->valid = 0; - p->i_bytes = 0; - p->n_bytes = 0; - -out: - if (out_buf) - *out_buf = res ? : &ucs4_null; - return len; -} - -/* - * Command Parser - * The ctl-seq parser "term_parser" only detects whole sequences, it does not - * detect the specific command. Once a sequence is parsed, the command-parsers - * are used to figure out their meaning. Note that this depends on whether we - * run on the host or terminal side. - */ - -static unsigned int term_parse_host_control(const term_seq *seq) { - assert_return(seq, TERM_CMD_NONE); - - switch (seq->terminator) { - case 0x00: /* NUL */ - return TERM_CMD_NULL; - case 0x05: /* ENQ */ - return TERM_CMD_ENQ; - case 0x07: /* BEL */ - return TERM_CMD_BEL; - case 0x08: /* BS */ - return TERM_CMD_BS; - case 0x09: /* HT */ - return TERM_CMD_HT; - case 0x0a: /* LF */ - return TERM_CMD_LF; - case 0x0b: /* VT */ - return TERM_CMD_VT; - case 0x0c: /* FF */ - return TERM_CMD_FF; - case 0x0d: /* CR */ - return TERM_CMD_CR; - case 0x0e: /* SO */ - return TERM_CMD_SO; - case 0x0f: /* SI */ - return TERM_CMD_SI; - case 0x11: /* DC1 */ - return TERM_CMD_DC1; - case 0x13: /* DC3 */ - return TERM_CMD_DC3; - case 0x18: /* CAN */ - /* this is already handled by the state-machine */ - break; - case 0x1a: /* SUB */ - return TERM_CMD_SUB; - case 0x1b: /* ESC */ - /* this is already handled by the state-machine */ - break; - case 0x1f: /* DEL */ - /* this is already handled by the state-machine */ - break; - case 0x84: /* IND */ - return TERM_CMD_IND; - case 0x85: /* NEL */ - return TERM_CMD_NEL; - case 0x88: /* HTS */ - return TERM_CMD_HTS; - case 0x8d: /* RI */ - return TERM_CMD_RI; - case 0x8e: /* SS2 */ - return TERM_CMD_SS2; - case 0x8f: /* SS3 */ - return TERM_CMD_SS3; - case 0x90: /* DCS */ - /* this is already handled by the state-machine */ - break; - case 0x96: /* SPA */ - return TERM_CMD_SPA; - case 0x97: /* EPA */ - return TERM_CMD_EPA; - case 0x98: /* SOS */ - /* this is already handled by the state-machine */ - break; - case 0x9a: /* DECID */ - return TERM_CMD_DECID; - case 0x9b: /* CSI */ - /* this is already handled by the state-machine */ - break; - case 0x9c: /* ST */ - return TERM_CMD_ST; - case 0x9d: /* OSC */ - /* this is already handled by the state-machine */ - break; - case 0x9e: /* PM */ - /* this is already handled by the state-machine */ - break; - case 0x9f: /* APC */ - /* this is already handled by the state-machine */ - break; - } - - return TERM_CMD_NONE; -} - -static inline int charset_from_cmd(uint32_t raw, unsigned int flags, bool require_96) { - static const struct { - uint32_t raw; - unsigned int flags; - } charset_cmds[] = { - /* 96-compat charsets */ - [TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL] = { .raw = 'A', .flags = 0 }, - [TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL] = { .raw = 'B', .flags = 0 }, - [TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL] = { .raw = 'M', .flags = 0 }, - [TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL] = { .raw = 'F', .flags = 0 }, - [TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL] = { .raw = 'H', .flags = 0 }, - [TERM_CHARSET_ISO_LATIN_CYRILLIC] = { .raw = 'L', .flags = 0 }, - - /* 94-compat charsets */ - [TERM_CHARSET_DEC_SPECIAL_GRAPHIC] = { .raw = '0', .flags = 0 }, - [TERM_CHARSET_DEC_SUPPLEMENTAL] = { .raw = '5', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_DEC_TECHNICAL] = { .raw = '>', .flags = 0 }, - [TERM_CHARSET_CYRILLIC_DEC] = { .raw = '4', .flags = TERM_SEQ_FLAG_AND }, - [TERM_CHARSET_DUTCH_NRCS] = { .raw = '4', .flags = 0 }, - [TERM_CHARSET_FINNISH_NRCS] = { .raw = '5', .flags = 0 }, - [TERM_CHARSET_FRENCH_NRCS] = { .raw = 'R', .flags = 0 }, - [TERM_CHARSET_FRENCH_CANADIAN_NRCS] = { .raw = '9', .flags = 0 }, - [TERM_CHARSET_GERMAN_NRCS] = { .raw = 'K', .flags = 0 }, - [TERM_CHARSET_GREEK_DEC] = { .raw = '?', .flags = TERM_SEQ_FLAG_DQUOTE }, - [TERM_CHARSET_GREEK_NRCS] = { .raw = '>', .flags = TERM_SEQ_FLAG_DQUOTE }, - [TERM_CHARSET_HEBREW_DEC] = { .raw = '4', .flags = TERM_SEQ_FLAG_DQUOTE }, - [TERM_CHARSET_HEBREW_NRCS] = { .raw = '=', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_ITALIAN_NRCS] = { .raw = 'Y', .flags = 0 }, - [TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = '`', .flags = 0 }, - [TERM_CHARSET_PORTUGUESE_NRCS] = { .raw = '6', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_RUSSIAN_NRCS] = { .raw = '5', .flags = TERM_SEQ_FLAG_AND }, - [TERM_CHARSET_SCS_NRCS] = { .raw = '3', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_SPANISH_NRCS] = { .raw = 'Z', .flags = 0 }, - [TERM_CHARSET_SWEDISH_NRCS] = { .raw = '7', .flags = 0 }, - [TERM_CHARSET_SWISS_NRCS] = { .raw = '=', .flags = 0 }, - [TERM_CHARSET_TURKISH_DEC] = { .raw = '0', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_TURKISH_NRCS] = { .raw = '2', .flags = TERM_SEQ_FLAG_PERCENT }, - - /* special charsets */ - [TERM_CHARSET_USERPREF_SUPPLEMENTAL] = { .raw = '<', .flags = 0 }, - - /* secondary choices */ - [TERM_CHARSET_CNT + TERM_CHARSET_FINNISH_NRCS] = { .raw = 'C', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_FRENCH_NRCS] = { .raw = 'f', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_FRENCH_CANADIAN_NRCS] = { .raw = 'Q', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = 'E', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_SWEDISH_NRCS] = { .raw = 'H', .flags = 0 }, /* unused; conflicts with ISO_HEBREW */ - - /* tertiary choices */ - [TERM_CHARSET_CNT + TERM_CHARSET_CNT + TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = '6', .flags = 0 }, - }; - size_t i, cs; - - /* - * Secondary choice on SWEDISH_NRCS and primary choice on - * ISO_HEBREW_SUPPLEMENTAL have a conflict: raw=="H", flags==0. - * We always choose the ISO 96-compat set, which is what VT510 does. - */ - - for (i = 0; i < ELEMENTSOF(charset_cmds); ++i) { - if (charset_cmds[i].raw == raw && charset_cmds[i].flags == flags) { - cs = i; - while (cs >= TERM_CHARSET_CNT) - cs -= TERM_CHARSET_CNT; - - if (!require_96 || cs < TERM_CHARSET_96_CNT || cs >= TERM_CHARSET_94_CNT) - return cs; - } - } - - return -ENOENT; -} - -/* true if exactly one bit in @value is set */ -static inline bool exactly_one_bit_set(unsigned int value) { - return __builtin_popcount(value) == 1; -} - -static unsigned int term_parse_host_escape(const term_seq *seq, unsigned int *cs_out) { - unsigned int t, flags; - int cs; - - assert_return(seq, TERM_CMD_NONE); - - flags = seq->intermediates; - t = TERM_SEQ_FLAG_POPEN | TERM_SEQ_FLAG_PCLOSE | TERM_SEQ_FLAG_MULT | - TERM_SEQ_FLAG_PLUS | TERM_SEQ_FLAG_MINUS | TERM_SEQ_FLAG_DOT | - TERM_SEQ_FLAG_SLASH; - - if (exactly_one_bit_set(flags & t)) { - switch (flags & t) { - case TERM_SEQ_FLAG_POPEN: - case TERM_SEQ_FLAG_PCLOSE: - case TERM_SEQ_FLAG_MULT: - case TERM_SEQ_FLAG_PLUS: - cs = charset_from_cmd(seq->terminator, flags & ~t, false); - break; - case TERM_SEQ_FLAG_MINUS: - case TERM_SEQ_FLAG_DOT: - case TERM_SEQ_FLAG_SLASH: - cs = charset_from_cmd(seq->terminator, flags & ~t, true); - break; - default: - cs = -ENOENT; - break; - } - - if (cs >= 0) { - if (cs_out) - *cs_out = cs; - return TERM_CMD_SCS; - } - - /* looked like a charset-cmd but wasn't; continue */ - } - - switch (seq->terminator) { - case '3': - if (flags == TERM_SEQ_FLAG_HASH) /* DECDHL top-half */ - return TERM_CMD_DECDHL_TH; - break; - case '4': - if (flags == TERM_SEQ_FLAG_HASH) /* DECDHL bottom-half */ - return TERM_CMD_DECDHL_BH; - break; - case '5': - if (flags == TERM_SEQ_FLAG_HASH) /* DECSWL */ - return TERM_CMD_DECSWL; - break; - case '6': - if (flags == 0) /* DECBI */ - return TERM_CMD_DECBI; - else if (flags == TERM_SEQ_FLAG_HASH) /* DECDWL */ - return TERM_CMD_DECDWL; - break; - case '7': - if (flags == 0) /* DECSC */ - return TERM_CMD_DECSC; - break; - case '8': - if (flags == 0) /* DECRC */ - return TERM_CMD_DECRC; - else if (flags == TERM_SEQ_FLAG_HASH) /* DECALN */ - return TERM_CMD_DECALN; - break; - case '9': - if (flags == 0) /* DECFI */ - return TERM_CMD_DECFI; - break; - case '<': - if (flags == 0) /* DECANM */ - return TERM_CMD_DECANM; - break; - case '=': - if (flags == 0) /* DECKPAM */ - return TERM_CMD_DECKPAM; - break; - case '>': - if (flags == 0) /* DECKPNM */ - return TERM_CMD_DECKPNM; - break; - case '@': - if (flags == TERM_SEQ_FLAG_PERCENT) { - /* Select default character set */ - return TERM_CMD_XTERM_SDCS; - } - break; - case 'D': - if (flags == 0) /* IND */ - return TERM_CMD_IND; - break; - case 'E': - if (flags == 0) /* NEL */ - return TERM_CMD_NEL; - break; - case 'F': - if (flags == 0) /* Cursor to lower-left corner of screen */ - return TERM_CMD_XTERM_CLLHP; - else if (flags == TERM_SEQ_FLAG_SPACE) /* S7C1T */ - return TERM_CMD_S7C1T; - break; - case 'G': - if (flags == TERM_SEQ_FLAG_SPACE) { /* S8C1T */ - return TERM_CMD_S8C1T; - } else if (flags == TERM_SEQ_FLAG_PERCENT) { - /* Select UTF-8 character set */ - return TERM_CMD_XTERM_SUCS; - } - break; - case 'H': - if (flags == 0) /* HTS */ - return TERM_CMD_HTS; - break; - case 'L': - if (flags == TERM_SEQ_FLAG_SPACE) { - /* Set ANSI conformance level 1 */ - return TERM_CMD_XTERM_SACL1; - } - break; - case 'M': - if (flags == 0) { /* RI */ - return TERM_CMD_RI; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* Set ANSI conformance level 2 */ - return TERM_CMD_XTERM_SACL2; - } - break; - case 'N': - if (flags == 0) { /* SS2 */ - return TERM_CMD_SS2; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* Set ANSI conformance level 3 */ - return TERM_CMD_XTERM_SACL3; - } - break; - case 'O': - if (flags == 0) /* SS3 */ - return TERM_CMD_SS3; - break; - case 'P': - if (flags == 0) /* DCS: this is already handled by the state-machine */ - return 0; - break; - case 'V': - if (flags == 0) /* SPA */ - return TERM_CMD_SPA; - break; - case 'W': - if (flags == 0) /* EPA */ - return TERM_CMD_EPA; - break; - case 'X': - if (flags == 0) { /* SOS */ - /* this is already handled by the state-machine */ - break; - } - break; - case 'Z': - if (flags == 0) /* DECID */ - return TERM_CMD_DECID; - break; - case '[': - if (flags == 0) { /* CSI */ - /* this is already handled by the state-machine */ - break; - } - break; - case '\\': - if (flags == 0) /* ST */ - return TERM_CMD_ST; - break; - case ']': - if (flags == 0) { /* OSC */ - /* this is already handled by the state-machine */ - break; - } - break; - case '^': - if (flags == 0) { /* PM */ - /* this is already handled by the state-machine */ - break; - } - break; - case '_': - if (flags == 0) { /* APC */ - /* this is already handled by the state-machine */ - break; - } - break; - case 'c': - if (flags == 0) /* RIS */ - return TERM_CMD_RIS; - break; - case 'l': - if (flags == 0) /* Memory lock */ - return TERM_CMD_XTERM_MLHP; - break; - case 'm': - if (flags == 0) /* Memory unlock */ - return TERM_CMD_XTERM_MUHP; - break; - case 'n': - if (flags == 0) /* LS2 */ - return TERM_CMD_LS2; - break; - case 'o': - if (flags == 0) /* LS3 */ - return TERM_CMD_LS3; - break; - case '|': - if (flags == 0) /* LS3R */ - return TERM_CMD_LS3R; - break; - case '}': - if (flags == 0) /* LS2R */ - return TERM_CMD_LS2R; - break; - case '~': - if (flags == 0) /* LS1R */ - return TERM_CMD_LS1R; - break; - } - - return TERM_CMD_NONE; -} - -static unsigned int term_parse_host_csi(const term_seq *seq) { - unsigned int flags; - - assert_return(seq, TERM_CMD_NONE); - - flags = seq->intermediates; - - switch (seq->terminator) { - case 'A': - if (flags == 0) /* CUU */ - return TERM_CMD_CUU; - break; - case 'a': - if (flags == 0) /* HPR */ - return TERM_CMD_HPR; - break; - case 'B': - if (flags == 0) /* CUD */ - return TERM_CMD_CUD; - break; - case 'b': - if (flags == 0) /* REP */ - return TERM_CMD_REP; - break; - case 'C': - if (flags == 0) /* CUF */ - return TERM_CMD_CUF; - break; - case 'c': - if (flags == 0) /* DA1 */ - return TERM_CMD_DA1; - else if (flags == TERM_SEQ_FLAG_GT) /* DA2 */ - return TERM_CMD_DA2; - else if (flags == TERM_SEQ_FLAG_EQUAL) /* DA3 */ - return TERM_CMD_DA3; - break; - case 'D': - if (flags == 0) /* CUB */ - return TERM_CMD_CUB; - break; - case 'd': - if (flags == 0) /* VPA */ - return TERM_CMD_VPA; - break; - case 'E': - if (flags == 0) /* CNL */ - return TERM_CMD_CNL; - break; - case 'e': - if (flags == 0) /* VPR */ - return TERM_CMD_VPR; - break; - case 'F': - if (flags == 0) /* CPL */ - return TERM_CMD_CPL; - break; - case 'f': - if (flags == 0) /* HVP */ - return TERM_CMD_HVP; - break; - case 'G': - if (flags == 0) /* CHA */ - return TERM_CMD_CHA; - break; - case 'g': - if (flags == 0) /* TBC */ - return TERM_CMD_TBC; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECLFKC */ - return TERM_CMD_DECLFKC; - break; - case 'H': - if (flags == 0) /* CUP */ - return TERM_CMD_CUP; - break; - case 'h': - if (flags == 0) /* SM ANSI */ - return TERM_CMD_SM_ANSI; - else if (flags == TERM_SEQ_FLAG_WHAT) /* SM DEC */ - return TERM_CMD_SM_DEC; - break; - case 'I': - if (flags == 0) /* CHT */ - return TERM_CMD_CHT; - break; - case 'i': - if (flags == 0) /* MC ANSI */ - return TERM_CMD_MC_ANSI; - else if (flags == TERM_SEQ_FLAG_WHAT) /* MC DEC */ - return TERM_CMD_MC_DEC; - break; - case 'J': - if (flags == 0) /* ED */ - return TERM_CMD_ED; - else if (flags == TERM_SEQ_FLAG_WHAT) /* DECSED */ - return TERM_CMD_DECSED; - break; - case 'K': - if (flags == 0) /* EL */ - return TERM_CMD_EL; - else if (flags == TERM_SEQ_FLAG_WHAT) /* DECSEL */ - return TERM_CMD_DECSEL; - break; - case 'L': - if (flags == 0) /* IL */ - return TERM_CMD_IL; - break; - case 'l': - if (flags == 0) /* RM ANSI */ - return TERM_CMD_RM_ANSI; - else if (flags == TERM_SEQ_FLAG_WHAT) /* RM DEC */ - return TERM_CMD_RM_DEC; - break; - case 'M': - if (flags == 0) /* DL */ - return TERM_CMD_DL; - break; - case 'm': - if (flags == 0) /* SGR */ - return TERM_CMD_SGR; - else if (flags == TERM_SEQ_FLAG_GT) /* XTERM SMR */ - return TERM_CMD_XTERM_SRV; - break; - case 'n': - if (flags == 0) /* DSR ANSI */ - return TERM_CMD_DSR_ANSI; - else if (flags == TERM_SEQ_FLAG_GT) /* XTERM RMR */ - return TERM_CMD_XTERM_RRV; - else if (flags == TERM_SEQ_FLAG_WHAT) /* DSR DEC */ - return TERM_CMD_DSR_DEC; - break; - case 'P': - if (flags == 0) /* DCH */ - return TERM_CMD_DCH; - else if (flags == TERM_SEQ_FLAG_SPACE) /* PPA */ - return TERM_CMD_PPA; - break; - case 'p': - if (flags == 0) /* DECSSL */ - return TERM_CMD_DECSSL; - else if (flags == TERM_SEQ_FLAG_SPACE) /* DECSSCLS */ - return TERM_CMD_DECSSCLS; - else if (flags == TERM_SEQ_FLAG_BANG) /* DECSTR */ - return TERM_CMD_DECSTR; - else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECSCL */ - return TERM_CMD_DECSCL; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECRQM-ANSI */ - return TERM_CMD_DECRQM_ANSI; - else if (flags == (TERM_SEQ_FLAG_CASH | TERM_SEQ_FLAG_WHAT)) /* DECRQM-DEC */ - return TERM_CMD_DECRQM_DEC; - else if (flags == TERM_SEQ_FLAG_PCLOSE) /* DECSDPT */ - return TERM_CMD_DECSDPT; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSPPCS */ - return TERM_CMD_DECSPPCS; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECSR */ - return TERM_CMD_DECSR; - else if (flags == TERM_SEQ_FLAG_COMMA) /* DECLTOD */ - return TERM_CMD_DECLTOD; - else if (flags == TERM_SEQ_FLAG_GT) /* XTERM SPM */ - return TERM_CMD_XTERM_SPM; - break; - case 'Q': - if (flags == TERM_SEQ_FLAG_SPACE) /* PPR */ - return TERM_CMD_PPR; - break; - case 'q': - if (flags == 0) /* DECLL */ - return TERM_CMD_DECLL; - else if (flags == TERM_SEQ_FLAG_SPACE) /* DECSCUSR */ - return TERM_CMD_DECSCUSR; - else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECSCA */ - return TERM_CMD_DECSCA; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECSDDT */ - return TERM_CMD_DECSDDT; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSRC */ - return TERM_CMD_DECSR; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECELF */ - return TERM_CMD_DECELF; - else if (flags == TERM_SEQ_FLAG_COMMA) /* DECTID */ - return TERM_CMD_DECTID; - break; - case 'R': - if (flags == TERM_SEQ_FLAG_SPACE) /* PPB */ - return TERM_CMD_PPB; - break; - case 'r': - if (flags == 0) { - /* DECSTBM */ - return TERM_CMD_DECSTBM; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* DECSKCV */ - return TERM_CMD_DECSKCV; - } else if (flags == TERM_SEQ_FLAG_CASH) { - /* DECCARA */ - return TERM_CMD_DECCARA; - } else if (flags == TERM_SEQ_FLAG_MULT) { - /* DECSCS */ - return TERM_CMD_DECSCS; - } else if (flags == TERM_SEQ_FLAG_PLUS) { - /* DECSMKR */ - return TERM_CMD_DECSMKR; - } else if (flags == TERM_SEQ_FLAG_WHAT) { - /* - * There's a conflict between DECPCTERM and XTERM-RPM. - * XTERM-RPM takes a single argument, DECPCTERM takes 2. - * Split both up and forward the call to the closer - * match. - */ - if (seq->n_args <= 1) /* XTERM RPM */ - return TERM_CMD_XTERM_RPM; - else if (seq->n_args >= 2) /* DECPCTERM */ - return TERM_CMD_DECPCTERM; - } - break; - case 'S': - if (flags == 0) /* SU */ - return TERM_CMD_SU; - else if (flags == TERM_SEQ_FLAG_WHAT) /* XTERM SGFX */ - return TERM_CMD_XTERM_SGFX; - break; - case 's': - if (flags == 0) { - /* - * There's a conflict between DECSLRM and SC-ANSI which - * cannot be resolved without knowing the state of - * DECLRMM. We leave that decision up to the caller. - */ - return TERM_CMD_DECSLRM_OR_SC; - } else if (flags == TERM_SEQ_FLAG_CASH) { - /* DECSPRTT */ - return TERM_CMD_DECSPRTT; - } else if (flags == TERM_SEQ_FLAG_MULT) { - /* DECSFC */ - return TERM_CMD_DECSFC; - } else if (flags == TERM_SEQ_FLAG_WHAT) { - /* XTERM SPM */ - return TERM_CMD_XTERM_SPM; - } - break; - case 'T': - if (flags == 0) { - /* - * Awesome: There's a conflict between SD and XTERM IHMT - * that we have to resolve by checking the parameter - * count.. XTERM_IHMT needs exactly 5 arguments, SD - * takes 0 or 1. We're conservative here and give both - * a wider range to allow unused arguments (compat...). - */ - if (seq->n_args >= 5) { - /* XTERM IHMT */ - return TERM_CMD_XTERM_IHMT; - } else if (seq->n_args < 5) { - /* SD */ - return TERM_CMD_SD; - } - } else if (flags == TERM_SEQ_FLAG_GT) { - /* XTERM RTM */ - return TERM_CMD_XTERM_RTM; - } - break; - case 't': - if (flags == 0) { - if (seq->n_args > 0 && seq->args[0] < 24) { - /* XTERM WM */ - return TERM_CMD_XTERM_WM; - } else { - /* DECSLPP */ - return TERM_CMD_DECSLPP; - } - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* DECSWBV */ - return TERM_CMD_DECSWBV; - } else if (flags == TERM_SEQ_FLAG_DQUOTE) { - /* DECSRFR */ - return TERM_CMD_DECSRFR; - } else if (flags == TERM_SEQ_FLAG_CASH) { - /* DECRARA */ - return TERM_CMD_DECRARA; - } else if (flags == TERM_SEQ_FLAG_GT) { - /* XTERM STM */ - return TERM_CMD_XTERM_STM; - } - break; - case 'U': - if (flags == 0) /* NP */ - return TERM_CMD_NP; - break; - case 'u': - if (flags == 0) { - /* RC */ - return TERM_CMD_RC; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* DECSMBV */ - return TERM_CMD_DECSMBV; - } else if (flags == TERM_SEQ_FLAG_DQUOTE) { - /* DECSTRL */ - return TERM_CMD_DECSTRL; - } else if (flags == TERM_SEQ_FLAG_WHAT) { - /* DECRQUPSS */ - return TERM_CMD_DECRQUPSS; - } else if (seq->args[0] == 1 && flags == TERM_SEQ_FLAG_CASH) { - /* DECRQTSR */ - return TERM_CMD_DECRQTSR; - } else if (flags == TERM_SEQ_FLAG_MULT) { - /* DECSCP */ - return TERM_CMD_DECSCP; - } else if (flags == TERM_SEQ_FLAG_COMMA) { - /* DECRQKT */ - return TERM_CMD_DECRQKT; - } - break; - case 'V': - if (flags == 0) /* PP */ - return TERM_CMD_PP; - break; - case 'v': - if (flags == TERM_SEQ_FLAG_SPACE) /* DECSLCK */ - return TERM_CMD_DECSLCK; - else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECRQDE */ - return TERM_CMD_DECRQDE; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECCRA */ - return TERM_CMD_DECCRA; - else if (flags == TERM_SEQ_FLAG_COMMA) /* DECRPKT */ - return TERM_CMD_DECRPKT; - break; - case 'W': - if (seq->args[0] == 5 && flags == TERM_SEQ_FLAG_WHAT) { - /* DECST8C */ - return TERM_CMD_DECST8C; - } - break; - case 'w': - if (flags == TERM_SEQ_FLAG_CASH) /* DECRQPSR */ - return TERM_CMD_DECRQPSR; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECEFR */ - return TERM_CMD_DECEFR; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECSPP */ - return TERM_CMD_DECSPP; - break; - case 'X': - if (flags == 0) /* ECH */ - return TERM_CMD_ECH; - break; - case 'x': - if (flags == 0) /* DECREQTPARM */ - return TERM_CMD_DECREQTPARM; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECFRA */ - return TERM_CMD_DECFRA; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSACE */ - return TERM_CMD_DECSACE; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECRQPKFM */ - return TERM_CMD_DECRQPKFM; - break; - case 'y': - if (flags == 0) /* DECTST */ - return TERM_CMD_DECTST; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECRQCRA */ - return TERM_CMD_DECRQCRA; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECPKFMR */ - return TERM_CMD_DECPKFMR; - break; - case 'Z': - if (flags == 0) /* CBT */ - return TERM_CMD_CBT; - break; - case 'z': - if (flags == TERM_SEQ_FLAG_CASH) /* DECERA */ - return TERM_CMD_DECERA; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECELR */ - return TERM_CMD_DECELR; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECINVM */ - return TERM_CMD_DECINVM; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECPKA */ - return TERM_CMD_DECPKA; - break; - case '@': - if (flags == 0) /* ICH */ - return TERM_CMD_ICH; - break; - case '`': - if (flags == 0) /* HPA */ - return TERM_CMD_HPA; - break; - case '{': - if (flags == TERM_SEQ_FLAG_CASH) /* DECSERA */ - return TERM_CMD_DECSERA; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECSLE */ - return TERM_CMD_DECSLE; - break; - case '|': - if (flags == TERM_SEQ_FLAG_CASH) /* DECSCPP */ - return TERM_CMD_DECSCPP; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECRQLP */ - return TERM_CMD_DECRQLP; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSNLS */ - return TERM_CMD_DECSNLS; - break; - case '}': - if (flags == TERM_SEQ_FLAG_SPACE) /* DECKBD */ - return TERM_CMD_DECKBD; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECSASD */ - return TERM_CMD_DECSASD; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECIC */ - return TERM_CMD_DECIC; - break; - case '~': - if (flags == TERM_SEQ_FLAG_SPACE) /* DECTME */ - return TERM_CMD_DECTME; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECSSDT */ - return TERM_CMD_DECSSDT; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECDC */ - return TERM_CMD_DECDC; - break; - } - - return TERM_CMD_NONE; -} - -/* - * State Machine - * This parser controls the parser-state and returns any detected sequence to - * the caller. The parser is based on this state-diagram from Paul Williams: - * http://vt100.net/emu/ - * It was written from scratch and extended where needed. - * This parser is fully compatible up to the vt500 series. We expect UCS-4 as - * input. It's the callers responsibility to do any UTF-8 parsing. - */ - -enum parser_state { - STATE_NONE, /* placeholder */ - STATE_GROUND, /* initial state and ground */ - STATE_ESC, /* ESC sequence was started */ - STATE_ESC_INT, /* intermediate escape characters */ - STATE_CSI_ENTRY, /* starting CSI sequence */ - STATE_CSI_PARAM, /* CSI parameters */ - STATE_CSI_INT, /* intermediate CSI characters */ - STATE_CSI_IGNORE, /* CSI error; ignore this CSI sequence */ - STATE_DCS_ENTRY, /* starting DCS sequence */ - STATE_DCS_PARAM, /* DCS parameters */ - STATE_DCS_INT, /* intermediate DCS characters */ - STATE_DCS_PASS, /* DCS data passthrough */ - STATE_DCS_IGNORE, /* DCS error; ignore this DCS sequence */ - STATE_OSC_STRING, /* parsing OSC sequence */ - STATE_ST_IGNORE, /* unimplemented seq; ignore until ST */ - STATE_NUM -}; - -enum parser_action { - ACTION_NONE, /* placeholder */ - ACTION_CLEAR, /* clear parameters */ - ACTION_IGNORE, /* ignore the character entirely */ - ACTION_PRINT, /* print the character on the console */ - ACTION_EXECUTE, /* execute single control character (C0/C1) */ - ACTION_COLLECT, /* collect intermediate character */ - ACTION_PARAM, /* collect parameter character */ - ACTION_ESC_DISPATCH, /* dispatch escape sequence */ - ACTION_CSI_DISPATCH, /* dispatch csi sequence */ - ACTION_DCS_START, /* start of DCS data */ - ACTION_DCS_COLLECT, /* collect DCS data */ - ACTION_DCS_CONSUME, /* consume DCS terminator */ - ACTION_DCS_DISPATCH, /* dispatch dcs sequence */ - ACTION_OSC_START, /* start of OSC data */ - ACTION_OSC_COLLECT, /* collect OSC data */ - ACTION_OSC_CONSUME, /* consume OSC terminator */ - ACTION_OSC_DISPATCH, /* dispatch osc sequence */ - ACTION_NUM -}; - -int term_parser_new(term_parser **out, bool host) { - _term_parser_free_ term_parser *parser = NULL; - - assert_return(out, -EINVAL); - - parser = new0(term_parser, 1); - if (!parser) - return -ENOMEM; - - parser->is_host = host; - parser->st_alloc = 64; - parser->seq.st = new0(char, parser->st_alloc + 1); - if (!parser->seq.st) - return -ENOMEM; - - *out = parser; - parser = NULL; - return 0; -} - -term_parser *term_parser_free(term_parser *parser) { - if (!parser) - return NULL; - - free(parser->seq.st); - free(parser); - return NULL; -} - -static inline void parser_clear(term_parser *parser) { - unsigned int i; - - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = 0; - parser->seq.intermediates = 0; - parser->seq.charset = TERM_CHARSET_NONE; - parser->seq.n_args = 0; - for (i = 0; i < TERM_PARSER_ARG_MAX; ++i) - parser->seq.args[i] = -1; - - parser->seq.n_st = 0; - parser->seq.st[0] = 0; -} - -static int parser_ignore(term_parser *parser, uint32_t raw) { - parser_clear(parser); - parser->seq.type = TERM_SEQ_IGNORE; - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - - return parser->seq.type; -} - -static int parser_print(term_parser *parser, uint32_t raw) { - parser_clear(parser); - parser->seq.type = TERM_SEQ_GRAPHIC; - parser->seq.command = TERM_CMD_GRAPHIC; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - - return parser->seq.type; -} - -static int parser_execute(term_parser *parser, uint32_t raw) { - parser_clear(parser); - parser->seq.type = TERM_SEQ_CONTROL; - parser->seq.command = TERM_CMD_GRAPHIC; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - if (!parser->is_host) - parser->seq.command = term_parse_host_control(&parser->seq); - - return parser->seq.type; -} - -static void parser_collect(term_parser *parser, uint32_t raw) { - /* - * Usually, characters from 0x30 to 0x3f are only allowed as leading - * markers (or as part of the parameters), characters from 0x20 to 0x2f - * are only allowed as trailing markers. However, our state-machine - * already verifies those restrictions so we can handle them the same - * way here. Note that we safely allow markers to be specified multiple - * times. - */ - - if (raw >= 0x20 && raw <= 0x3f) - parser->seq.intermediates |= 1 << (raw - 0x20); -} - -static void parser_param(term_parser *parser, uint32_t raw) { - int new; - - if (raw == ';') { - if (parser->seq.n_args < TERM_PARSER_ARG_MAX) - ++parser->seq.n_args; - - return; - } - - if (parser->seq.n_args >= TERM_PARSER_ARG_MAX) - return; - - if (raw >= '0' && raw <= '9') { - new = parser->seq.args[parser->seq.n_args]; - if (new < 0) - new = 0; - new = new * 10 + raw - '0'; - - /* VT510 tells us to clamp all values to [0, 9999], however, it - * also allows commands with values up to 2^15-1. We simply use - * 2^16 as maximum here to be compatible to all commands, but - * avoid overflows in any calculations. */ - if (new > 0xffff) - new = 0xffff; - - parser->seq.args[parser->seq.n_args] = new; - } -} - -static int parser_esc(term_parser *parser, uint32_t raw) { - parser->seq.type = TERM_SEQ_ESCAPE; - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - if (!parser->is_host) - parser->seq.command = term_parse_host_escape(&parser->seq, &parser->seq.charset); - - return parser->seq.type; -} - -static int parser_csi(term_parser *parser, uint32_t raw) { - /* parser->seq is cleared during CSI-ENTER state, thus there's no need - * to clear invalid fields here. */ - - if (parser->seq.n_args < TERM_PARSER_ARG_MAX) { - if (parser->seq.n_args > 0 || - parser->seq.args[parser->seq.n_args] >= 0) - ++parser->seq.n_args; - } - - parser->seq.type = TERM_SEQ_CSI; - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - if (!parser->is_host) - parser->seq.command = term_parse_host_csi(&parser->seq); - - return parser->seq.type; -} - -/* perform state transition and dispatch related actions */ -static int parser_transition(term_parser *parser, uint32_t raw, unsigned int state, unsigned int action) { - if (state != STATE_NONE) - parser->state = state; - - switch (action) { - case ACTION_NONE: - return TERM_SEQ_NONE; - case ACTION_CLEAR: - parser_clear(parser); - return TERM_SEQ_NONE; - case ACTION_IGNORE: - return parser_ignore(parser, raw); - case ACTION_PRINT: - return parser_print(parser, raw); - case ACTION_EXECUTE: - return parser_execute(parser, raw); - case ACTION_COLLECT: - parser_collect(parser, raw); - return TERM_SEQ_NONE; - case ACTION_PARAM: - parser_param(parser, raw); - return TERM_SEQ_NONE; - case ACTION_ESC_DISPATCH: - return parser_esc(parser, raw); - case ACTION_CSI_DISPATCH: - return parser_csi(parser, raw); - case ACTION_DCS_START: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_DCS_COLLECT: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_DCS_CONSUME: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_DCS_DISPATCH: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_START: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_COLLECT: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_CONSUME: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_DISPATCH: - /* not implemented */ - return TERM_SEQ_NONE; - default: - assert_not_reached("invalid vte-parser action"); - return TERM_SEQ_NONE; - } -} - -static int parser_feed_to_state(term_parser *parser, uint32_t raw) { - switch (parser->state) { - case STATE_NONE: - /* - * During initialization, parser->state is cleared. Treat this - * as STATE_GROUND. We will then never get to STATE_NONE again. - */ - case STATE_GROUND: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - case 0x80 ... 0x9b: /* C1 \ { ST } */ - case 0x9d ... 0x9f: - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_PRINT); - case STATE_ESC: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_ESC_INT, ACTION_COLLECT); - case 0x30 ... 0x4f: /* ['0' - '~'] \ { 'P', 'X', '[', ']', '^', '_' } */ - case 0x51 ... 0x57: - case 0x59 ... 0x5a: - case 0x5c: - case 0x60 ... 0x7e: - return parser_transition(parser, raw, STATE_GROUND, ACTION_ESC_DISPATCH); - case 0x50: /* 'P' */ - return parser_transition(parser, raw, STATE_DCS_ENTRY, ACTION_CLEAR); - case 0x5b: /* '[' */ - return parser_transition(parser, raw, STATE_CSI_ENTRY, ACTION_CLEAR); - case 0x5d: /* ']' */ - return parser_transition(parser, raw, STATE_OSC_STRING, ACTION_CLEAR); - case 0x58: /* 'X' */ - case 0x5e: /* '^' */ - case 0x5f: /* '_' */ - return parser_transition(parser, raw, STATE_ST_IGNORE, ACTION_NONE); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_ESC_INT, ACTION_COLLECT); - case STATE_ESC_INT: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case 0x30 ... 0x7e: /* ['0' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_ESC_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case STATE_CSI_ENTRY: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_CSI_INT, ACTION_COLLECT); - case 0x3a: /* ':' */ - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_CSI_PARAM, ACTION_PARAM); - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_CSI_PARAM, ACTION_COLLECT); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case STATE_CSI_PARAM: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_CSI_INT, ACTION_COLLECT); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_NONE, ACTION_PARAM); - case 0x3a: /* ':' */ - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case STATE_CSI_INT: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case 0x30 ... 0x3f: /* ['0' - '?'] */ - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case STATE_CSI_IGNORE: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x3f: /* [' ' - '?'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_NONE); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - case STATE_DCS_ENTRY: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_DCS_INT, ACTION_COLLECT); - case 0x3a: /* ':' */ - return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_DCS_PARAM, ACTION_PARAM); - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_DCS_PARAM, ACTION_COLLECT); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case STATE_DCS_PARAM: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_DCS_INT, ACTION_COLLECT); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_NONE, ACTION_PARAM); - case 0x3a: /* ':' */ - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case STATE_DCS_INT: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case 0x30 ... 0x3f: /* ['0' - '?'] */ - return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case STATE_DCS_PASS: - switch (raw) { - case 0x00 ... 0x7e: /* ASCII \ { DEL } */ - return parser_transition(parser, raw, STATE_NONE, ACTION_DCS_COLLECT); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_DCS_DISPATCH); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_DCS_COLLECT); - case STATE_DCS_IGNORE: - switch (raw) { - case 0x00 ... 0x7f: /* ASCII */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_NONE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - case STATE_OSC_STRING: - switch (raw) { - case 0x00 ... 0x06: /* C0 \ { BEL } */ - case 0x08 ... 0x1f: - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x7f: /* [' ' - DEL] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_OSC_COLLECT); - case 0x07: /* BEL */ - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_OSC_DISPATCH); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_OSC_COLLECT); - case STATE_ST_IGNORE: - switch (raw) { - case 0x00 ... 0x7f: /* ASCII */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - } - - assert_not_reached("bad vte-parser state"); - return -EINVAL; -} - -int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw) { - int r; - - assert_return(parser, -EINVAL); - assert_return(seq_out, -EINVAL); - - /* - * Notes: - * * DEC treats GR codes as GL. We don't do that as we require UTF-8 - * as charset and, thus, it doesn't make sense to treat GR special. - * * During control sequences, unexpected C1 codes cancel the sequence - * and immediately start a new one. C0 codes, however, may or may not - * be ignored/executed depending on the sequence. - */ - - switch (raw) { - case 0x18: /* CAN */ - r = parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - break; - case 0x1a: /* SUB */ - r = parser_transition(parser, raw, STATE_GROUND, ACTION_EXECUTE); - break; - case 0x80 ... 0x8f: /* C1 \ {DCS, SOS, CSI, ST, OSC, PM, APC} */ - case 0x91 ... 0x97: - case 0x99 ... 0x9a: - r = parser_transition(parser, raw, STATE_GROUND, ACTION_EXECUTE); - break; - case 0x1b: /* ESC */ - r = parser_transition(parser, raw, STATE_ESC, ACTION_CLEAR); - break; - case 0x98: /* SOS */ - case 0x9e: /* PM */ - case 0x9f: /* APC */ - r = parser_transition(parser, raw, STATE_ST_IGNORE, ACTION_NONE); - break; - case 0x90: /* DCS */ - r = parser_transition(parser, raw, STATE_DCS_ENTRY, ACTION_CLEAR); - break; - case 0x9d: /* OSC */ - r = parser_transition(parser, raw, STATE_OSC_STRING, ACTION_CLEAR); - break; - case 0x9b: /* CSI */ - r = parser_transition(parser, raw, STATE_CSI_ENTRY, ACTION_CLEAR); - break; - default: - r = parser_feed_to_state(parser, raw); - break; - } - - if (r <= 0) - *seq_out = NULL; - else - *seq_out = &parser->seq; - - return r; -} diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c deleted file mode 100644 index 0e38ff41c6..0000000000 --- a/src/libsystemd-terminal/term-screen.c +++ /dev/null @@ -1,4333 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Terminal Screens - * The term_screen layer implements the terminal-side. It handles all commands - * returned by the seq-parser and applies them to its own pages. - * - * While there are a lot of legacy control-sequences, we only support a small - * subset. There is no reason to implement unused codes like horizontal - * scrolling. - * If you implement new commands, make sure to document them properly. - * - * Standards: - * ECMA-48 - * ANSI X3.64 - * ISO/IEC 6429 - * References: - * http://www.vt100.net/emu/ctrlseq_dec.html - * http://www.vt100.net/docs/vt100-ug/chapter3.html - * http://www.vt100.net/docs/vt510-rm/chapter4 - * http://www.vt100.net/docs/vt510-rm/contents - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * ASCII - * http://en.wikipedia.org/wiki/C0_and_C1_control_codes - * https://en.wikipedia.org/wiki/ANSI_color - */ - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include <xkbcommon/xkbcommon-keysyms.h> -#include "macro.h" -#include "term-internal.h" -#include "util.h" -#include "utf8.h" - -int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data) { - _cleanup_(term_screen_unrefp) term_screen *screen = NULL; - int r; - - assert_return(out, -EINVAL); - - screen = new0(term_screen, 1); - if (!screen) - return -ENOMEM; - - screen->ref = 1; - screen->age = 1; - screen->write_fn = write_fn; - screen->write_fn_data = write_fn_data; - screen->cmd_fn = cmd_fn; - screen->cmd_fn_data = cmd_fn_data; - screen->flags = TERM_FLAG_7BIT_MODE; - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; - screen->g0 = &term_unicode_lower; - screen->g1 = &term_unicode_upper; - screen->g2 = &term_unicode_lower; - screen->g3 = &term_unicode_upper; - screen->state.gl = &screen->g0; - screen->state.gr = &screen->g1; - screen->saved = screen->state; - screen->saved_alt = screen->saved; - - r = term_page_new(&screen->page_main); - if (r < 0) - return r; - - r = term_page_new(&screen->page_alt); - if (r < 0) - return r; - - r = term_parser_new(&screen->parser, false); - if (r < 0) - return r; - - r = term_history_new(&screen->history_main); - if (r < 0) - return r; - - screen->page = screen->page_main; - screen->history = screen->history_main; - - *out = screen; - screen = NULL; - return 0; -} - -term_screen *term_screen_ref(term_screen *screen) { - if (!screen) - return NULL; - - assert_return(screen->ref > 0, NULL); - - ++screen->ref; - return screen; -} - -term_screen *term_screen_unref(term_screen *screen) { - if (!screen) - return NULL; - - assert_return(screen->ref > 0, NULL); - - if (--screen->ref) - return NULL; - - free(screen->answerback); - free(screen->tabs); - term_history_free(screen->history_main); - term_page_free(screen->page_alt); - term_page_free(screen->page_main); - term_parser_free(screen->parser); - free(screen); - - return NULL; -} - -/* - * Write-Helpers - * Unfortunately, 7bit/8bit compat mode requires us to send C1 controls encoded - * as 7bit if asked by the application. This is really used in the wild, so we - * cannot fall back to "always 7bit". - * screen_write() is the underlying backend which forwards any writes to the - * users's callback. It's the users responsibility to buffer these and write - * them out once their call to term_screen_feed_*() returns. - * The SEQ_WRITE() and SEQ_WRITE_KEY() macros allow constructing C0/C1 sequences - * directly in the code-base without requiring any intermediate buffer during - * runtime. - */ - -#define C0_CSI "\e[" -#define C1_CSI "\x9b" - -#define SEQ(_screen, _prefix_esc, _c0, _c1, _seq) \ - (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \ - ((_prefix_esc) ? ("\e" _c0 _seq) : (_c0 _seq)) : \ - ((_prefix_esc) ? ("\e" _c1 _seq) : (_c1 _seq))) - -#define SEQ_SIZE(_screen, _prefix_esc, _c0, _c1, _seq) \ - (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \ - ((_prefix_esc) ? sizeof("\e" _c0 _seq) : sizeof(_c0 _seq)) : \ - ((_prefix_esc) ? sizeof("\e" _c1 _seq) : sizeof(_c1 _seq))) - -#define SEQ_WRITE_KEY(_screen, _prefix_esc, _c0, _c1, _seq) \ - screen_write((_screen), \ - SEQ((_screen), (_prefix_esc), \ - _c0, _c1, _seq), \ - SEQ_SIZE((_screen), (_prefix_esc), \ - _c0, _c1, _seq) - 1) - -#define SEQ_WRITE(_screen, _c0, _c1, _seq) \ - SEQ_WRITE_KEY((_screen), false, _c0, _c1, _seq) - -static int screen_write(term_screen *screen, const void *buf, size_t len) { - if (len < 1 || !screen->write_fn) - return 0; - - return screen->write_fn(screen, screen->write_fn_data, buf, len); -} - -/* - * Command Forwarding - * Some commands cannot be handled by the screen-layer directly. Those are - * forwarded to the command-handler of the caller. This is rarely used and can - * safely be set to NULL. - */ - -static int screen_forward(term_screen *screen, unsigned int cmd, const term_seq *seq) { - if (!screen->cmd_fn) - return 0; - - return screen->cmd_fn(screen, screen->cmd_fn_data, cmd, seq); -} - -/* - * Screen Helpers - * These helpers implement common-operations like cursor-handler and more, which - * are used by several command dispatchers. - */ - -static unsigned int screen_clamp_x(term_screen *screen, unsigned int x) { - if (x >= screen->page->width) - return (screen->page->width > 0) ? screen->page->width - 1 : 0; - - return x; -} - -static unsigned int screen_clamp_y(term_screen *screen, unsigned int y) { - if (y >= screen->page->height) - return (screen->page->height > 0) ? screen->page->height - 1 : 0; - - return y; -} - -static bool screen_tab_is_set(term_screen *screen, unsigned int pos) { - if (pos >= screen->page->width) - return false; - - return screen->tabs[pos / 8] & (1 << (pos % 8)); -} - -static inline void screen_age_cursor(term_screen *screen) { - term_cell *cell; - - cell = term_page_get_cell(screen->page, screen->state.cursor_x, screen->state.cursor_y); - if (cell) - cell->age = screen->age; -} - -static void screen_cursor_clear_wrap(term_screen *screen) { - screen->flags &= ~TERM_FLAG_PENDING_WRAP; -} - -static void screen_cursor_set(term_screen *screen, unsigned int x, unsigned int y) { - x = screen_clamp_x(screen, x); - y = screen_clamp_y(screen, y); - - if (x == screen->state.cursor_x && y == screen->state.cursor_y) - return; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - - screen->state.cursor_x = x; - screen->state.cursor_y = y; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); -} - -static void screen_cursor_set_rel(term_screen *screen, unsigned int x, unsigned int y) { - if (screen->state.origin_mode) { - x = screen_clamp_x(screen, x); - y = screen_clamp_x(screen, y) + screen->page->scroll_idx; - - if (y >= screen->page->scroll_idx + screen->page->scroll_num) { - y = screen->page->scroll_idx + screen->page->scroll_num; - if (screen->page->scroll_num > 0) - y -= 1; - } - } - - screen_cursor_set(screen, x, y); -} - -static void screen_cursor_left(term_screen *screen, unsigned int num) { - if (num > screen->state.cursor_x) - num = screen->state.cursor_x; - - screen_cursor_set(screen, screen->state.cursor_x - num, screen->state.cursor_y); -} - -static void screen_cursor_left_tab(term_screen *screen, unsigned int num) { - unsigned int i; - - i = screen->state.cursor_x; - while (i > 0 && num > 0) { - if (screen_tab_is_set(screen, --i)) - --num; - } - - screen_cursor_set(screen, i, screen->state.cursor_y); -} - -static void screen_cursor_right(term_screen *screen, unsigned int num) { - if (num > screen->page->width) - num = screen->page->width; - - screen_cursor_set(screen, screen->state.cursor_x + num, screen->state.cursor_y); -} - -static void screen_cursor_right_tab(term_screen *screen, unsigned int num) { - unsigned int i; - - i = screen->state.cursor_x; - while (i + 1 < screen->page->width && num > 0) { - if (screen_tab_is_set(screen, ++i)) - --num; - } - - screen_cursor_set(screen, i, screen->state.cursor_y); -} - -static void screen_cursor_up(term_screen *screen, unsigned int num, bool scroll) { - unsigned int max; - - if (screen->state.cursor_y < screen->page->scroll_idx) { - if (num > screen->state.cursor_y) - num = screen->state.cursor_y; - - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y - num); - } else { - max = screen->state.cursor_y - screen->page->scroll_idx; - if (num > max) { - if (num < 1) - return; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - - if (scroll) - term_page_scroll_down(screen->page, num - max, &screen->state.attr, screen->age, NULL); - - screen->state.cursor_y = screen->page->scroll_idx; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - } else { - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y - num); - } - } -} - -static void screen_cursor_down(term_screen *screen, unsigned int num, bool scroll) { - unsigned int max; - - if (screen->state.cursor_y >= screen->page->scroll_idx + screen->page->scroll_num) { - if (num > screen->page->height) - num = screen->page->height; - - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y - num); - } else { - max = screen->page->scroll_idx + screen->page->scroll_num - 1 - screen->state.cursor_y; - if (num > max) { - if (num < 1) - return; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - - if (scroll) - term_page_scroll_up(screen->page, num - max, &screen->state.attr, screen->age, screen->history); - - screen->state.cursor_y = screen->page->scroll_idx + screen->page->scroll_num - 1; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - } else { - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y + num); - } - } -} - -static void screen_save_state(term_screen *screen, term_state *where) { - *where = screen->state; -} - -static void screen_restore_state(term_screen *screen, term_state *from) { - screen_cursor_set(screen, from->cursor_x, from->cursor_y); - screen->state = *from; -} - -static void screen_reset_page(term_screen *screen, term_page *page) { - term_page_set_scroll_region(page, 0, page->height); - term_page_erase(page, 0, 0, page->width, page->height, &screen->state.attr, screen->age, false); -} - -static void screen_change_alt(term_screen *screen, bool set) { - if (set) { - screen->page = screen->page_alt; - screen->history = NULL; - } else { - screen->page = screen->page_main; - screen->history = screen->history_main; - } - - screen->page->age = screen->age; -} - -static inline void set_reset(term_screen *screen, unsigned int flag, bool set) { - if (set) - screen->flags |= flag; - else - screen->flags &= ~flag; -} - -static void screen_mode_change_ansi(term_screen *screen, unsigned mode, bool set) { - switch (mode) { - case 20: - /* - * LNM: line-feed/new-line mode - * TODO - */ - set_reset(screen, TERM_FLAG_NEWLINE_MODE, set); - - break; - default: - log_debug("terminal: failed to %s unknown ANSI mode %u", set ? "set" : "unset", mode); - } -} - -static void screen_mode_change_dec(term_screen *screen, unsigned int mode, bool set) { - switch (mode) { - case 1: - /* - * DECCKM: cursor-keys - * TODO - */ - set_reset(screen, TERM_FLAG_CURSOR_KEYS, set); - - break; - case 6: - /* - * DECOM: origin-mode - * TODO - */ - screen->state.origin_mode = set; - - break; - case 7: - /* - * DECAWN: auto-wrap mode - * TODO - */ - screen->state.auto_wrap = set; - - break; - case 25: - /* - * DECTCEM: text-cursor-enable - * TODO - */ - set_reset(screen, TERM_FLAG_HIDE_CURSOR, !set); - screen_age_cursor(screen); - - break; - case 47: - /* - * XTERM-ASB: alternate-screen-buffer - * This enables/disables the alternate screen-buffer. - * It effectively saves the current page content and - * allows you to restore it when changing to the - * original screen-buffer again. - */ - screen_change_alt(screen, set); - - break; - case 1047: - /* - * XTERM-ASBPE: alternate-screen-buffer-post-erase - * This is the same as XTERM-ASB but erases the - * alternate screen-buffer before switching back to the - * original buffer. Use it to discard any data on the - * alternate screen buffer when done. - */ - if (!set) - screen_reset_page(screen, screen->page_alt); - - screen_change_alt(screen, set); - - break; - case 1048: - /* - * XTERM-ASBCS: alternate-screen-buffer-cursor-state - * This has the same effect as DECSC/DECRC, but uses a - * separate state buffer. It is usually used in - * combination with alternate screen buffers to provide - * stacked state storage. - */ - if (set) - screen_save_state(screen, &screen->saved_alt); - else - screen_restore_state(screen, &screen->saved_alt); - - break; - case 1049: - /* - * XTERM-ASBX: alternate-screen-buffer-extended - * This combines XTERM-ASBPE and XTERM-ASBCS somewhat. - * When enabling, state is saved, alternate screen - * buffer is activated and cleared. - * When disabled, alternate screen buffer is cleared, - * then normal screen buffer is enabled and state is - * restored. - */ - if (set) - screen_save_state(screen, &screen->saved_alt); - - screen_reset_page(screen, screen->page_alt); - screen_change_alt(screen, set); - - if (!set) - screen_restore_state(screen, &screen->saved_alt); - - break; - default: - log_debug("terminal: failed to %s unknown DEC mode %u", set ? "set" : "unset", mode); - } -} - -/* map a character according to current GL and GR maps */ -static uint32_t screen_map(term_screen *screen, uint32_t val) { - uint32_t nval = -1U; - - /* 32 and 127 always map to identity. 160 and 255 map to identity iff a - * 96 character set is loaded into GR. Values above 255 always map to - * identity. */ - switch (val) { - case 33 ... 126: - if (screen->state.glt) { - nval = (**screen->state.glt)[val - 32]; - screen->state.glt = NULL; - } else { - nval = (**screen->state.gl)[val - 32]; - } - break; - case 160 ... 255: - if (screen->state.grt) { - nval = (**screen->state.grt)[val - 160]; - screen->state.grt = NULL; - } else { - nval = (**screen->state.gr)[val - 160]; - } - break; - } - - return (nval == -1U) ? val : nval; -} - -/* - * Command Handlers - * This is the unofficial documentation of all the TERM_CMD_* definitions. Each - * handled command has a separate function with an extensive comment on the - * semantics of the command. - * Note that many semantics are unknown and need to be verified. This is mostly - * about error-handling, though. Applications rarely rely on those features. - */ - -static int screen_DA1(term_screen *screen, const term_seq *seq); -static int screen_LF(term_screen *screen, const term_seq *seq); - -static int screen_GRAPHIC(term_screen *screen, const term_seq *seq) { - term_char_t ch = TERM_CHAR_NULL; - - if (screen->state.cursor_x + 1 == screen->page->width - && screen->flags & TERM_FLAG_PENDING_WRAP - && screen->state.auto_wrap) { - screen_cursor_down(screen, 1, true); - screen_cursor_set(screen, 0, screen->state.cursor_y); - } - - screen_cursor_clear_wrap(screen); - - ch = term_char_merge(ch, screen_map(screen, seq->terminator)); - term_page_write(screen->page, screen->state.cursor_x, screen->state.cursor_y, ch, 1, &screen->state.attr, screen->age, false); - - if (screen->state.cursor_x + 1 == screen->page->width) - screen->flags |= TERM_FLAG_PENDING_WRAP; - else - screen_cursor_right(screen, 1); - - return 0; -} - -static int screen_BEL(term_screen *screen, const term_seq *seq) { - /* - * BEL - sound bell tone - * This command should trigger an acoustic bell. Usually, this is - * forwarded directly to the pcspkr. However, bells have become quite - * uncommon and annoying, so we're not implementing them here. Instead, - * it's one of the commands we forward to the caller. - */ - - return screen_forward(screen, TERM_CMD_BEL, seq); -} - -static int screen_BS(term_screen *screen, const term_seq *seq) { - /* - * BS - backspace - * Move cursor one cell to the left. If already at the left margin, - * nothing happens. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_left(screen, 1); - return 0; -} - -static int screen_CBT(term_screen *screen, const term_seq *seq) { - /* - * CBT - cursor-backward-tabulation - * Move the cursor @args[0] tabs backwards (to the left). The - * current cursor cell, in case it's a tab, is not counted. - * Furthermore, the cursor cannot be moved beyond position 0 and - * it will stop there. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_left_tab(screen, num); - - return 0; -} - -static int screen_CHA(term_screen *screen, const term_seq *seq) { - /* - * CHA - cursor-horizontal-absolute - * Move the cursor to position @args[0] in the current line. The - * cursor cannot be moved beyond the rightmost cell and will stop - * there. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int pos = 1; - - if (seq->args[0] > 0) - pos = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, pos - 1, screen->state.cursor_y); - - return 0; -} - -static int screen_CHT(term_screen *screen, const term_seq *seq) { - /* - * CHT - cursor-horizontal-forward-tabulation - * Move the cursor @args[0] tabs forward (to the right). The - * current cursor cell, in case it's a tab, is not counted. - * Furthermore, the cursor cannot be moved beyond the rightmost cell - * and will stop there. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_right_tab(screen, num); - - return 0; -} - -static int screen_CNL(term_screen *screen, const term_seq *seq) { - /* - * CNL - cursor-next-line - * Move the cursor @args[0] lines down. - * - * TODO: Does this stop at the bottom or cause a scroll-up? - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, num, false); - - return 0; -} - -static int screen_CPL(term_screen *screen, const term_seq *seq) { - /* - * CPL - cursor-preceding-line - * Move the cursor @args[0] lines up. - * - * TODO: Does this stop at the top or cause a scroll-up? - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_up(screen, num, false); - - return 0; -} - -static int screen_CR(term_screen *screen, const term_seq *seq) { - /* - * CR - carriage-return - * Move the cursor to the left margin on the current line. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, 0, screen->state.cursor_y); - - return 0; -} - -static int screen_CUB(term_screen *screen, const term_seq *seq) { - /* - * CUB - cursor-backward - * Move the cursor @args[0] positions to the left. The cursor stops - * at the left-most position. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_left(screen, num); - - return 0; -} - -static int screen_CUD(term_screen *screen, const term_seq *seq) { - /* - * CUD - cursor-down - * Move the cursor @args[0] positions down. The cursor stops at the - * bottom margin. If it was already moved further, it stops at the - * bottom line. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, num, false); - - return 0; -} - -static int screen_CUF(term_screen *screen, const term_seq *seq) { - /* - * CUF -cursor-forward - * Move the cursor @args[0] positions to the right. The cursor stops - * at the right-most position. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_right(screen, num); - - return 0; -} - -static int screen_CUP(term_screen *screen, const term_seq *seq) { - /* - * CUP - cursor-position - * Moves the cursor to position @args[1] x @args[0]. If either is 0, it - * is treated as 1. The positions are subject to the origin-mode and - * clamped to the addressable with/height. - * - * Defaults: - * args[0]: 1 - * args[1]: 1 - */ - - unsigned int x = 1, y = 1; - - if (seq->args[0] > 0) - y = seq->args[0]; - if (seq->args[1] > 0) - x = seq->args[1]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set_rel(screen, x - 1, y - 1); - - return 0; -} - -static int screen_CUU(term_screen *screen, const term_seq *seq) { - /* - * CUU - cursor-up - * Move the cursor @args[0] positions up. The cursor stops at the - * top margin. If it was already moved further, it stops at the - * top line. - * - * Defaults: - * args[0]: 1 - * - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_up(screen, num, false); - - return 0; -} - -static int screen_DA1(term_screen *screen, const term_seq *seq) { - /* - * DA1 - primary-device-attributes - * The primary DA asks for basic terminal features. We simply return - * a hard-coded list of features we implement. - * Note that the primary DA asks for supported features, not currently - * enabled features. - * - * The terminal's answer is: - * ^[ ? 64 ; ARGS c - * The first argument, 64, is fixed and denotes a VT420, the last - * DEC-term that extended this number. - * All following arguments denote supported features. Note - * that at most 15 features can be sent (max CSI args). It is safe to - * send more, but clients might not be able to parse them. This is a - * client's problem and we shouldn't care. There is no other way to - * send those feature lists, so we have to extend them beyond 15 in - * those cases. - * - * Known modes: - * 1: 132 column mode - * The 132 column mode is supported by the terminal. - * 2: printer port - * A priner-port is supported and can be addressed via - * control-codes. - * 3: ReGIS graphics - * Support for ReGIS graphics is available. The ReGIS routines - * provide the "remote graphics instruction set" and allow basic - * vector-rendering. - * 4: sixel - * Support of Sixel graphics is available. This provides access - * to the sixel bitmap routines. - * 6: selective erase - * The terminal supports DECSCA and related selective-erase - * functions. This allows to protect specific cells from being - * erased, if specified. - * 7: soft character set (DRCS) - * TODO: ? - * 8: user-defined keys (UDKs) - * TODO: ? - * 9: national-replacement character sets (NRCS) - * National-replacement character-sets are available. - * 12: Yugoslavian (SCS) - * TODO: ? - * 15: technical character set - * The DEC technical-character-set is available. - * 18: windowing capability - * TODO: ? - * 21: horizontal scrolling - * TODO: ? - * 22: ANSII color - * TODO: ? - * 23: Greek - * TODO: ? - * 24: Turkish - * TODO: ? - * 29: ANSI text locator - * TODO: ? - * 42: ISO Latin-2 character set - * TODO: ? - * 44: PCTerm - * TODO: ? - * 45: soft keymap - * TODO: ? - * 46: ASCII emulation - * TODO: ? - */ - - return SEQ_WRITE(screen, C0_CSI, C1_CSI, "?64;1;6;9;15c"); -} - -static int screen_DA2(term_screen *screen, const term_seq *seq) { - /* - * DA2 - secondary-device-attributes - * The secondary DA asks for the terminal-ID, firmware versions and - * other non-primary attributes. All these values are - * informational-only and should not be used by the host to detect - * terminal features. - * - * The terminal's response is: - * ^[ > 61 ; FIRMWARE ; KEYBOARD c - * whereas 65 is fixed for VT525 terminals, the last terminal-line that - * increased this number. FIRMWARE is the firmware - * version encoded as major/minor (20 == 2.0) and KEYBOARD is 0 for STD - * keyboard and 1 for PC keyboards. - * - * We replace the firmware-version with the systemd-version so clients - * can decode it again. - */ - - return SEQ_WRITE(screen, C0_CSI, C1_CSI, ">65;" PACKAGE_VERSION ";1c"); -} - -static int screen_DA3(term_screen *screen, const term_seq *seq) { - /* - * DA3 - tertiary-device-attributes - * The tertiary DA is used to query the terminal-ID. - * - * The terminal's response is: - * ^P ! | XX AA BB CC ^\ - * whereas all four parameters are hexadecimal-encoded pairs. XX - * denotes the manufacturing site, AA BB CC is the terminal's ID. - */ - - /* we do not support tertiary DAs */ - return 0; -} - -static int screen_DC1(term_screen *screen, const term_seq *seq) { - /* - * DC1 - device-control-1 or XON - * This clears any previous XOFF and resumes terminal-transmission. - */ - - /* we do not support XON */ - return 0; -} - -static int screen_DC3(term_screen *screen, const term_seq *seq) { - /* - * DC3 - device-control-3 or XOFF - * Stops terminal transmission. No further characters are sent until - * an XON is received. - */ - - /* we do not support XOFF */ - return 0; -} - -static int screen_DCH(term_screen *screen, const term_seq *seq) { - /* - * DCH - delete-character - * This deletes @argv[0] characters at the current cursor position. As - * characters are deleted, the remaining characters between the cursor - * and right margin move to the left. Character attributes move with the - * characters. The terminal adds blank spaces with no visual character - * attributes at the right margin. DCH has no effect outside the - * scrolling margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - term_page_delete_cells(screen->page, screen->state.cursor_x, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_DECALN(term_screen *screen, const term_seq *seq) { - /* - * DECALN - screen-alignment-pattern - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECANM(term_screen *screen, const term_seq *seq) { - /* - * DECANM - ansi-mode - * Set the terminal into VT52 compatibility mode. Control sequences - * overlap with regular sequences so we have to detect them early before - * dispatching them. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECBI(term_screen *screen, const term_seq *seq) { - /* - * DECBI - back-index - * This control function moves the cursor backward one column. If the - * cursor is at the left margin, then all screen data within the margin - * moves one column to the right. The column that shifted past the right - * margin is lost. - * DECBI adds a new column at the left margin with no visual attributes. - * DECBI does not affect the margins. If the cursor is beyond the - * left-margin at the left border, then the terminal ignores DECBI. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECCARA(term_screen *screen, const term_seq *seq) { - /* - * DECCARA - change-attributes-in-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECCRA(term_screen *screen, const term_seq *seq) { - /* - * DECCRA - copy-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDC(term_screen *screen, const term_seq *seq) { - /* - * DECDC - delete-column - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDHL_BH(term_screen *screen, const term_seq *seq) { - /* - * DECDHL_BH - double-width-double-height-line: bottom half - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDHL_TH(term_screen *screen, const term_seq *seq) { - /* - * DECDHL_TH - double-width-double-height-line: top half - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDWL(term_screen *screen, const term_seq *seq) { - /* - * DECDWL - double-width-single-height-line - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECEFR(term_screen *screen, const term_seq *seq) { - /* - * DECEFR - enable-filter-rectangle - * Defines the coordinates of a filter rectangle (top, left, bottom, - * right as @args[0] to @args[3]) and activates it. - * Anytime the locator is detected outside of the filter rectangle, an - * outside rectangle event is generated and the rectangle is disabled. - * Filter rectangles are always treated as "one-shot" events. Any - * parameters that are omitted default to the current locator position. - * If all parameters are omitted, any locator motion will be reported. - * DECELR always cancels any prevous rectangle definition. - * - * The locator is usually associated with the mouse-cursor, but based - * on cells instead of pixels. See DECELR how to initialize and enable - * it. DECELR can also enable pixel-mode instead of cell-mode. - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECELF(term_screen *screen, const term_seq *seq) { - /* - * DECELF - enable-local-functions - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECELR(term_screen *screen, const term_seq *seq) { - /* - * DECELR - enable-locator-reporting - * This changes the locator-reporting mode. @args[0] specifies the mode - * to set, 0 disables locator-reporting, 1 enables it continuously, 2 - * enables it for a single report. @args[1] specifies the - * precision-mode. 0 and 2 set the reporting to cell-precision, 1 sets - * pixel-precision. - * - * Defaults: - * args[0]: 0 - * args[1]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECERA(term_screen *screen, const term_seq *seq) { - /* - * DECERA - erase-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECFI(term_screen *screen, const term_seq *seq) { - /* - * DECFI - forward-index - * This control function moves the cursor forward one column. If the - * cursor is at the right margin, then all screen data within the - * margins moves one column to the left. The column shifted past the - * left margin is lost. - * DECFI adds a new column at the right margin, with no visual - * attributes. DECFI does not affect margins. If the cursor is beyond - * the right margin at the border of the page when the terminal - * receives DECFI, then the terminal ignores DECFI. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECFRA(term_screen *screen, const term_seq *seq) { - /* - * DECFRA - fill-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECIC(term_screen *screen, const term_seq *seq) { - /* - * DECIC - insert-column - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECID(term_screen *screen, const term_seq *seq) { - /* - * DECID - return-terminal-id - * This is an obsolete form of TERM_CMD_DA1. - */ - - return screen_DA1(screen, seq); -} - -static int screen_DECINVM(term_screen *screen, const term_seq *seq) { - /* - * DECINVM - invoke-macro - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECKBD(term_screen *screen, const term_seq *seq) { - /* - * DECKBD - keyboard-language-selection - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECKPAM(term_screen *screen, const term_seq *seq) { - /* - * DECKPAM - keypad-application-mode - * Enables the keypad-application mode. If enabled, the keypad sends - * special characters instead of the printed characters. This way, - * applications can detect whether a numeric key was pressed on the - * top-row or on the keypad. - * Default is keypad-numeric-mode. - */ - - screen->flags |= TERM_FLAG_KEYPAD_MODE; - - return 0; -} - -static int screen_DECKPNM(term_screen *screen, const term_seq *seq) { - /* - * DECKPNM - keypad-numeric-mode - * This disables the keypad-application-mode (DECKPAM) and returns to - * the keypad-numeric-mode. Keypresses on the keypad generate the same - * sequences as corresponding keypresses on the main keyboard. - * Default is keypad-numeric-mode. - */ - - screen->flags &= ~TERM_FLAG_KEYPAD_MODE; - - return 0; -} - -static int screen_DECLFKC(term_screen *screen, const term_seq *seq) { - /* - * DECLFKC - local-function-key-control - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECLL(term_screen *screen, const term_seq *seq) { - /* - * DECLL - load-leds - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECLTOD(term_screen *screen, const term_seq *seq) { - /* - * DECLTOD - load-time-of-day - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECPCTERM(term_screen *screen, const term_seq *seq) { - /* - * DECPCTERM - pcterm-mode - * This enters/exits the PCTerm mode. Default mode is VT-mode. It can - * also select parameters for scancode/keycode mappings in SCO mode. - * - * Definitely not worth implementing. Lets kill PCTerm/SCO modes! - */ - - return 0; -} - -static int screen_DECPKA(term_screen *screen, const term_seq *seq) { - /* - * DECPKA - program-key-action - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECPKFMR(term_screen *screen, const term_seq *seq) { - /* - * DECPKFMR - program-key-free-memory-report - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRARA(term_screen *screen, const term_seq *seq) { - /* - * DECRARA - reverse-attributes-in-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRC(term_screen *screen, const term_seq *seq) { - /* - * DECRC - restore-cursor - * Restores the terminal to the state saved by the save cursor (DECSC) - * function. This includes more than just the cursor-position. - * - * If nothing was saved by DECSC, then DECRC performs the following - * actions: - * * Moves the cursor to the home position (upper left of screen). - * * Resets origin mode (DECOM). - * * Turns all character attributes off (normal setting). - * * Maps the ASCII character set into GL, and the DEC Supplemental - * Graphic set into GR. - * - * The terminal maintains a separate DECSC buffer for the main display - * and the status line. This feature lets you save a separate operating - * state for the main display and the status line. - */ - - screen_restore_state(screen, &screen->saved); - - return 0; -} - -static int screen_DECREQTPARM(term_screen *screen, const term_seq *seq) { - /* - * DECREQTPARM - request-terminal-parameters - * The sequence DECREPTPARM is sent by the terminal controller to notify - * the host of the status of selected terminal parameters. The status - * sequence may be sent when requested by the host or at the terminal's - * discretion. DECREPTPARM is sent upon receipt of a DECREQTPARM. - * - * If @args[0] is 0, this marks a request and the terminal is allowed - * to send DECREPTPARM messages without request. If it is 1, the same - * applies but the terminal should no longer send DECREPTPARM - * unrequested. - * 2 and 3 mark a report, but 3 is only used if the terminal answers as - * an explicit request with @args[0] == 1. - * - * The other arguments are ignored in requests, but have the following - * meaning in responses: - * args[1]: 1=no-parity-set 4=parity-set-and-odd 5=parity-set-and-even - * args[2]: 1=8bits-per-char 2=7bits-per-char - * args[3]: transmission-speed - * args[4]: receive-speed - * args[5]: 1=bit-rate-multiplier-is-16 - * args[6]: This value communicates the four switch values in block 5 - * of SETUP B, which are only visible to the user when an STP - * option is installed. These bits may be assigned for an STP - * device. The four bits are a decimal-encoded binary number. - * Value between 0-15. - * - * The transmission/receive speeds have mappings for number => bits/s - * which are quite weird. Examples are: 96->3600, 112->9600, 120->19200 - * - * Defaults: - * args[0]: 0 - */ - - if (seq->n_args < 1 || seq->args[0] == 0) { - screen->flags &= ~TERM_FLAG_INHIBIT_TPARM; - return SEQ_WRITE(screen, C0_CSI, C1_CSI, "2;1;1;120;120;1;0x"); - } else if (seq->args[0] == 1) { - screen->flags |= TERM_FLAG_INHIBIT_TPARM; - return SEQ_WRITE(screen, C0_CSI, C1_CSI, "3;1;1;120;120;1;0x"); - } else { - return 0; - } -} - -static int screen_DECRPKT(term_screen *screen, const term_seq *seq) { - /* - * DECRPKT - report-key-type - * Response to DECRQKT, we can safely ignore it as we're the one sending - * it to the host. - */ - - return 0; -} - -static int screen_DECRQCRA(term_screen *screen, const term_seq *seq) { - /* - * DECRQCRA - request-checksum-of-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQDE(term_screen *screen, const term_seq *seq) { - /* - * DECRQDE - request-display-extent - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQKT(term_screen *screen, const term_seq *seq) { - /* - * DECRQKT - request-key-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQLP(term_screen *screen, const term_seq *seq) { - /* - * DECRQLP - request-locator-position - * See DECELR for locator-information. - * - * TODO: document and implement - */ - - return 0; -} - -static int screen_DECRQM_ANSI(term_screen *screen, const term_seq *seq) { - /* - * DECRQM_ANSI - request-mode-ansi - * The host sends this control function to find out if a particular mode - * is set or reset. The terminal responds with a report mode function. - * @args[0] contains the mode to query. - * - * Response is DECRPM with the first argument set to the mode that was - * queried, second argument is 0 if mode is invalid, 1 if mode is set, - * 2 if mode is not set (reset), 3 if mode is permanently set and 4 if - * mode is permanently not set (reset): - * ANSI: ^[ MODE ; VALUE $ y - * DEC: ^[ ? MODE ; VALUE $ y - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECRQM_DEC(term_screen *screen, const term_seq *seq) { - /* - * DECRQM_DEC - request-mode-dec - * Same as DECRQM_ANSI but for DEC modes. - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECRQPKFM(term_screen *screen, const term_seq *seq) { - /* - * DECRQPKFM - request-program-key-free-memory - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQPSR(term_screen *screen, const term_seq *seq) { - /* - * DECRQPSR - request-presentation-state-report - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQTSR(term_screen *screen, const term_seq *seq) { - /* - * DECRQTSR - request-terminal-state-report - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQUPSS(term_screen *screen, const term_seq *seq) { - /* - * DECRQUPSS - request-user-preferred-supplemental-set - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSACE(term_screen *screen, const term_seq *seq) { - /* - * DECSACE - select-attribute-change-extent - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSASD(term_screen *screen, const term_seq *seq) { - /* - * DECSASD - select-active-status-display - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSC(term_screen *screen, const term_seq *seq) { - /* - * DECSC - save-cursor - * Save cursor and terminal state so it can be restored later on. - * Saves the following items in the terminal's memory: - * * Cursor position - * * Character attributes set by the SGR command - * * Character sets (G0, G1, G2, or G3) currently in GL and GR - * * Wrap flag (autowrap or no autowrap) - * * State of origin mode (DECOM) - * * Selective erase attribute - * * Any single shift 2 (SS2) or single shift 3 (SS3) functions sent - */ - - screen_save_state(screen, &screen->saved); - - return 0; -} - -static int screen_DECSCA(term_screen *screen, const term_seq *seq) { - /* - * DECSCA - select-character-protection-attribute - * Defines the characters that come after it as erasable or not erasable - * from the screen. The selective erase control functions (DECSED and - * DECSEL) can only erase characters defined as erasable. - * - * @args[0] specifies the new mode. 0 and 2 mark any following character - * as erasable, 1 marks it as not erasable. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - case 2: - screen->state.attr.protect = 0; - break; - case 1: - screen->state.attr.protect = 1; - break; - } - - return 0; -} - -static int screen_DECSCL(term_screen *screen, const term_seq *seq) { - /* - * DECSCL - select-conformance-level - * Select the terminal's operating level. The factory default is - * level 4 (VT Level 4 mode, 7-bit controls). - * When you change the conformance level, the terminal performs a hard - * reset (RIS). - * - * @args[0] defines the conformance-level, valid values are: - * 61: Level 1 (VT100) - * 62: Level 2 (VT200) - * 63: Level 3 (VT300) - * 64: Level 4 (VT400) - * @args[1] defines the 8bit-mode, valid values are: - * 0: 8-bit controls - * 1: 7-bit controls - * 2: 8-bit controls (same as 0) - * - * If @args[0] is 61, then @args[1] is ignored and 7bit controls are - * enforced. - * - * Defaults: - * args[0]: 64 - * args[1]: 0 - */ - - unsigned int level = 64, bit = 0; - - if (seq->n_args > 0) { - level = seq->args[0]; - if (seq->n_args > 1) - bit = seq->args[1]; - } - - term_screen_hard_reset(screen); - - switch (level) { - case 61: - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT100; - screen->flags |= TERM_FLAG_7BIT_MODE; - break; - case 62 ... 69: - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; - if (bit == 1) - screen->flags |= TERM_FLAG_7BIT_MODE; - else - screen->flags &= ~TERM_FLAG_7BIT_MODE; - break; - } - - return 0; -} - -static int screen_DECSCP(term_screen *screen, const term_seq *seq) { - /* - * DECSCP - select-communication-port - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSCPP(term_screen *screen, const term_seq *seq) { - /* - * DECSCPP - select-columns-per-page - * Select columns per page. The number of rows is unaffected by this. - * @args[0] selectes the number of columns (width), DEC only defines 80 - * and 132, but we allow any integer here. 0 is equivalent to 80. - * Page content is *not* cleared and the cursor is left untouched. - * However, if the page is reduced in width and the cursor would be - * outside the visible region, it's set to the right border. Newly added - * cells are cleared. No data is retained outside the visible region. - * - * Defaults: - * args[0]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSCS(term_screen *screen, const term_seq *seq) { - /* - * DECSCS - select-communication-speed - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSCUSR(term_screen *screen, const term_seq *seq) { - /* - * DECSCUSR - set-cursor-style - * This changes the style of the cursor. @args[0] can be one of: - * 0, 1: blinking block - * 2: steady block - * 3: blinking underline - * 4: steady underline - * Changing this setting does _not_ affect the cursor visibility itself. - * Use DECTCEM for that. - * - * Defaults: - * args[0]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSDDT(term_screen *screen, const term_seq *seq) { - /* - * DECSDDT - select-disconnect-delay-time - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSDPT(term_screen *screen, const term_seq *seq) { - /* - * DECSDPT - select-digital-printed-data-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSED(term_screen *screen, const term_seq *seq) { - /* - * DECSED - selective-erase-in-display - * This control function erases some or all of the erasable characters - * in the display. DECSED can only erase characters defined as erasable - * by the DECSCA control function. DECSED works inside or outside the - * scrolling margins. - * - * @args[0] defines which regions are erased. If it is 0, all cells from - * the cursor (inclusive) till the end of the display are erase. If it - * is 1, all cells from the start of the display till the cursor - * (inclusive) are erased. If it is 2, all cells are erased. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, true); - break; - case 1: - term_page_erase(screen->page, - 0, 0, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - case 2: - term_page_erase(screen->page, - 0, 0, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, true); - break; - } - - return 0; -} - -static int screen_DECSEL(term_screen *screen, const term_seq *seq) { - /* - * DECSEL - selective-erase-in-line - * This control function erases some or all of the erasable characters - * in a single line of text. DECSEL erases only those characters defined - * as erasable by the DECSCA control function. DECSEL works inside or - * outside the scrolling margins. - * - * @args[0] defines the region to be erased. If it is 0, all cells from - * the cursor (inclusive) till the end of the line are erase. If it is - * 1, all cells from the start of the line till the cursor (inclusive) - * are erased. If it is 2, the whole line of the cursor is erased. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - case 1: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - case 2: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - } - - return 0; -} - -static int screen_DECSERA(term_screen *screen, const term_seq *seq) { - /* - * DECSERA - selective-erase-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSFC(term_screen *screen, const term_seq *seq) { - /* - * DECSFC - select-flow-control - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSKCV(term_screen *screen, const term_seq *seq) { - /* - * DECSKCV - set-key-click-volume - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSLCK(term_screen *screen, const term_seq *seq) { - /* - * DECSLCK - set-lock-key-style - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSLE(term_screen *screen, const term_seq *seq) { - /* - * DECSLE - select-locator-events - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSLPP(term_screen *screen, const term_seq *seq) { - /* - * DECSLPP - set-lines-per-page - * Set the number of lines used for the page. @args[0] specifies the - * number of lines to be used. DEC only allows a limited number of - * choices, however, we allow all integers. 0 is equivalent to 24. - * - * Defaults: - * args[0]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSLRM_OR_SC(term_screen *screen, const term_seq *seq) { - /* - * DECSLRM_OR_SC - set-left-and-right-margins or save-cursor - * - * TODO: Detect save-cursor and run it. DECSLRM is not worth - * implementing. - */ - - return 0; -} - -static int screen_DECSMBV(term_screen *screen, const term_seq *seq) { - /* - * DECSMBV - set-margin-bell-volume - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSMKR(term_screen *screen, const term_seq *seq) { - /* - * DECSMKR - select-modifier-key-reporting - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSNLS(term_screen *screen, const term_seq *seq) { - /* - * DECSNLS - set-lines-per-screen - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSPP(term_screen *screen, const term_seq *seq) { - /* - * DECSPP - set-port-parameter - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSPPCS(term_screen *screen, const term_seq *seq) { - /* - * DECSPPCS - select-pro-printer-character-set - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSPRTT(term_screen *screen, const term_seq *seq) { - /* - * DECSPRTT - select-printer-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSR(term_screen *screen, const term_seq *seq) { - /* - * DECSR - secure-reset - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSRFR(term_screen *screen, const term_seq *seq) { - /* - * DECSRFR - select-refresh-rate - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSSCLS(term_screen *screen, const term_seq *seq) { - /* - * DECSSCLS - set-scroll-speed - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSSDT(term_screen *screen, const term_seq *seq) { - /* - * DECSSDT - select-status-display-line-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSSL(term_screen *screen, const term_seq *seq) { - /* - * DECSSL - select-setup-language - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECST8C(term_screen *screen, const term_seq *seq) { - /* - * DECST8C - set-tab-at-every-8-columns - * Clear the tab-ruler and reset it to a tab at every 8th column, - * starting at 9 (though, setting a tab at 1 is fine as it has no - * effect). - */ - - unsigned int i; - - for (i = 0; i < screen->page->width; i += 8) - screen->tabs[i / 8] = 0x1; - - return 0; -} - -static int screen_DECSTBM(term_screen *screen, const term_seq *seq) { - /* - * DECSTBM - set-top-and-bottom-margins - * This control function sets the top and bottom margins for the current - * page. You cannot perform scrolling outside the margins. - * - * @args[0] defines the top margin, @args[1] defines the bottom margin. - * The bottom margin must be lower than the top-margin. - * - * This call resets the cursor position to 0/0 of the page. - * - * Defaults: - * args[0]: 1 - * args[1]: last page-line - */ - - unsigned int top, bottom; - - top = 1; - bottom = screen->page->height; - - if (seq->args[0] > 0) - top = seq->args[0]; - if (seq->args[1] > 0) - bottom = seq->args[1]; - - if (top > screen->page->height) - top = screen->page->height; - if (bottom > screen->page->height) - bottom = screen->page->height; - - if (top >= bottom || top > screen->page->height || bottom > screen->page->height) { - top = 1; - bottom = screen->page->height; - } - - term_page_set_scroll_region(screen->page, top - 1, bottom - top + 1); - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, 0, 0); - - return 0; -} - -static int screen_DECSTR(term_screen *screen, const term_seq *seq) { - /* - * DECSTR - soft-terminal-reset - * Perform a soft reset to the default values. - */ - - term_screen_soft_reset(screen); - - return 0; -} - -static int screen_DECSTRL(term_screen *screen, const term_seq *seq) { - /* - * DECSTRL - set-transmit-rate-limit - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSWBV(term_screen *screen, const term_seq *seq) { - /* - * DECSWBV - set-warning-bell-volume - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSWL(term_screen *screen, const term_seq *seq) { - /* - * DECSWL - single-width-single-height-line - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECTID(term_screen *screen, const term_seq *seq) { - /* - * DECTID - select-terminal-id - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECTME(term_screen *screen, const term_seq *seq) { - /* - * DECTME - terminal-mode-emulation - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECTST(term_screen *screen, const term_seq *seq) { - /* - * DECTST - invoke-confidence-test - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DL(term_screen *screen, const term_seq *seq) { - /* - * DL - delete-line - * This control function deletes one or more lines in the scrolling - * region, starting with the line that has the cursor. @args[0] defines - * the number of lines to delete. 0 is treated the same as 1. - * As lines are deleted, lines below the cursor and in the scrolling - * region move up. The terminal adds blank lines with no visual - * character attributes at the bottom of the scrolling region. If it is - * greater than the number of lines remaining on the page, DL deletes - * only the remaining lines. DL has no effect outside the scrolling - * margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_delete_lines(screen->page, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_DSR_ANSI(term_screen *screen, const term_seq *seq) { - /* - * DSR_ANSI - device-status-report-ansi - * - * TODO: implement - */ - - return 0; -} - -static int screen_DSR_DEC(term_screen *screen, const term_seq *seq) { - /* - * DSR_DEC - device-status-report-dec - * - * TODO: implement - */ - - return 0; -} - -static int screen_ECH(term_screen *screen, const term_seq *seq) { - /* - * ECH - erase-character - * This control function erases one or more characters, from the cursor - * position to the right. ECH clears character attributes from erased - * character positions. ECH works inside or outside the scrolling - * margins. - * @args[0] defines the number of characters to erase. 0 is treated the - * same as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->state.cursor_x + num, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - - return 0; -} - -static int screen_ED(term_screen *screen, const term_seq *seq) { - /* - * ED - erase-in-display - * This control function erases characters from part or all of the - * display. When you erase complete lines, they become single-height, - * single-width lines, with all visual character attributes cleared. ED - * works inside or outside the scrolling margins. - * - * @args[0] defines the region to erase. 0 means from cursor (inclusive) - * till the end of the screen. 1 means from the start of the screen till - * the cursor (inclusive) and 2 means the whole screen. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, false); - break; - case 1: - term_page_erase(screen->page, - 0, 0, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - case 2: - term_page_erase(screen->page, - 0, 0, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, false); - break; - } - - return 0; -} - -static int screen_EL(term_screen *screen, const term_seq *seq) { - /* - * EL - erase-in-line - * This control function erases characters on the line that has the - * cursor. EL clears all character attributes from erased character - * positions. EL works inside or outside the scrolling margins. - * - * @args[0] defines the region to erase. 0 means from cursor (inclusive) - * till the end of the line. 1 means from the start of the line till the - * cursor (inclusive) and 2 means the whole line. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - case 1: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - case 2: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - } - - return 0; -} - -static int screen_ENQ(term_screen *screen, const term_seq *seq) { - /* - * ENQ - enquiry - * Transmit the answerback-string. If none is set, do nothing. - */ - - if (screen->answerback) - return screen_write(screen, screen->answerback, strlen(screen->answerback)); - - return 0; -} - -static int screen_EPA(term_screen *screen, const term_seq *seq) { - /* - * EPA - end-of-guarded-area - * - * TODO: What is this? - */ - - return 0; -} - -static int screen_FF(term_screen *screen, const term_seq *seq) { - /* - * FF - form-feed - * This causes the cursor to jump to the next line. It is treated the - * same as LF. - */ - - return screen_LF(screen, seq); -} - -static int screen_HPA(term_screen *screen, const term_seq *seq) { - /* - * HPA - horizontal-position-absolute - * HPA causes the active position to be moved to the n-th horizontal - * position of the active line. If an attempt is made to move the active - * position past the last position on the line, then the active position - * stops at the last position on the line. - * - * @args[0] defines the horizontal position. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, num - 1, screen->state.cursor_y); - - return 0; -} - -static int screen_HPR(term_screen *screen, const term_seq *seq) { - /* - * HPR - horizontal-position-relative - * HPR causes the active position to be moved to the n-th following - * horizontal position of the active line. If an attempt is made to move - * the active position past the last position on the line, then the - * active position stops at the last position on the line. - * - * @args[0] defines the horizontal position. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_right(screen, num); - - return 0; -} - -static int screen_HT(term_screen *screen, const term_seq *seq) { - /* - * HT - horizontal-tab - * Moves the cursor to the next tab stop. If there are no more tab - * stops, the cursor moves to the right margin. HT does not cause text - * to auto wrap. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_right_tab(screen, 1); - - return 0; -} - -static int screen_HTS(term_screen *screen, const term_seq *seq) { - /* - * HTS - horizontal-tab-set - * HTS sets a horizontal tab stop at the column position indicated by - * the value of the active column when the terminal receives an HTS. - * - * Executing an HTS does not effect the other horizontal tab stop - * settings. - */ - - unsigned int pos; - - pos = screen->state.cursor_x; - if (screen->page->width > 0) - screen->tabs[pos / 8] |= 1U << (pos % 8); - - return 0; -} - -static int screen_HVP(term_screen *screen, const term_seq *seq) { - /* - * HVP - horizontal-and-vertical-position - * This control function works the same as the cursor position (CUP) - * function. Origin mode (DECOM) selects line numbering and the ability - * to move the cursor into margins. - * - * Defaults: - * args[0]: 1 - * args[1]: 1 - */ - - return screen_CUP(screen, seq); -} - -static int screen_ICH(term_screen *screen, const term_seq *seq) { - /* - * ICH - insert-character - * This control function inserts one or more space (SP) characters - * starting at the cursor position. @args[0] is the number of characters - * to insert. 0 is treated as 1. - * - * The ICH sequence inserts blank characters with the normal - * character attribute. The cursor remains at the beginning of the blank - * characters. Text between the cursor and right margin moves to the - * right. Characters scrolled past the right margin are lost. ICH has no - * effect outside the scrolling margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - term_page_insert_cells(screen->page, screen->state.cursor_x, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_IL(term_screen *screen, const term_seq *seq) { - /* - * IL - insert-line - * This control function inserts one or more blank lines, starting at - * the cursor. @args[0] is the number of lines to insert. 0 is treated - * as 1. - * - * As lines are inserted, lines below the cursor and in the scrolling - * region move down. Lines scrolled off the page are lost. IL has no - * effect outside the page margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - term_page_insert_lines(screen->page, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_IND(term_screen *screen, const term_seq *seq) { - /* - * IND - index - * IND moves the cursor down one line in the same column. If the cursor - * is at the bottom margin, then the screen performs a scroll-up. - */ - - screen_cursor_down(screen, 1, true); - - return 0; -} - -static int screen_LF(term_screen *screen, const term_seq *seq) { - /* - * LF - line-feed - * Causes a line feed or a new line operation, depending on the setting - * of line feed/new line mode. - */ - - screen_cursor_down(screen, 1, true); - if (screen->flags & TERM_FLAG_NEWLINE_MODE) - screen_cursor_left(screen, screen->state.cursor_x); - - return 0; -} - -static int screen_LS1R(term_screen *screen, const term_seq *seq) { - /* - * LS1R - locking-shift-1-right - * Map G1 into GR. - */ - - screen->state.gr = &screen->g1; - - return 0; -} - -static int screen_LS2(term_screen *screen, const term_seq *seq) { - /* - * LS2 - locking-shift-2 - * Map G2 into GL. - */ - - screen->state.gl = &screen->g2; - - return 0; -} - -static int screen_LS2R(term_screen *screen, const term_seq *seq) { - /* - * LS2R - locking-shift-2-right - * Map G2 into GR. - */ - - screen->state.gr = &screen->g2; - - return 0; -} - -static int screen_LS3(term_screen *screen, const term_seq *seq) { - /* - * LS3 - locking-shift-3 - * Map G3 into GL. - */ - - screen->state.gl = &screen->g3; - - return 0; -} - -static int screen_LS3R(term_screen *screen, const term_seq *seq) { - /* - * LS3R - locking-shift-3-right - * Map G3 into GR. - */ - - screen->state.gr = &screen->g3; - - return 0; -} - -static int screen_MC_ANSI(term_screen *screen, const term_seq *seq) { - /* - * MC_ANSI - media-copy-ansi - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_MC_DEC(term_screen *screen, const term_seq *seq) { - /* - * MC_DEC - media-copy-dec - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_NEL(term_screen *screen, const term_seq *seq) { - /* - * NEL - next-line - * Moves cursor to first position on next line. If cursor is at bottom - * margin, then screen performs a scroll-up. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, 1, true); - screen_cursor_set(screen, 0, screen->state.cursor_y); - - return 0; -} - -static int screen_NP(term_screen *screen, const term_seq *seq) { - /* - * NP - next-page - * This control function moves the cursor forward to the home position - * on one of the following pages in page memory. If there is only one - * page, then the terminal ignores NP. - * If NP tries to move the cursor past the last page in memory, then the - * cursor stops at the last page. - * - * @args[0] defines the number of pages to forward. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_NULL(term_screen *screen, const term_seq *seq) { - /* - * NULL - null - * The NULL operation does nothing. ASCII NULL is always ignored. - */ - - return 0; -} - -static int screen_PP(term_screen *screen, const term_seq *seq) { - /* - * PP - preceding-page - * This control function moves the cursor backward to the home position - * on one of the preceding pages in page memory. If there is only one - * page, then the terminal ignores PP. - * If PP tries to move the cursor back farther than the first page in - * memory, then the cursor stops at the first page. - * - * @args[0] defines the number of pages to go backwards. 0 is treated - * as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_PPA(term_screen *screen, const term_seq *seq) { - /* - * PPA - page-position-absolute - * This control function can move the cursor to the corresponding row - * and column on any page in page memory. You select the page by its - * number. If there is only one page, then the terminal ignores PPA. - * - * @args[0] is the number of the page to move the cursor to. If it is - * greater than the number of the last page in memory, then the cursor - * stops at the last page. If it is less than the number of the first - * page, then the cursor stops at the first page. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_PPB(term_screen *screen, const term_seq *seq) { - /* - * PPB - page-position-backward - * This control function moves the cursor backward to the corresponding - * row and column on one of the preceding pages in page memory. If there - * is only one page, then the terminal ignores PPB. - * - * @args[0] indicates the number of pages to move the cursor backward. - * If it tries to move the cursor back farther than the first page in - * memory, then the cursor stops at the first page. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_PPR(term_screen *screen, const term_seq *seq) { - /* - * PPR - page-position-relative - * This control function moves the cursor forward to the corresponding - * row and column on one of the following pages in page memory. If there - * is only one page, then the terminal ignores PPR. - * - * @args[0] indicates how many pages to move the cursor forward. If it - * tries to move the cursor beyond the last page in memory, then the - * cursor stops at the last page. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_RC(term_screen *screen, const term_seq *seq) { - /* - * RC - restore-cursor - */ - - return screen_DECRC(screen, seq); -} - -static int screen_REP(term_screen *screen, const term_seq *seq) { - /* - * REP - repeat - * Repeat the preceding graphics-character the given number of times. - * @args[0] specifies how often it shall be repeated. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_RI(term_screen *screen, const term_seq *seq) { - /* - * RI - reverse-index - * Moves the cursor up one line in the same column. If the cursor is at - * the top margin, the page scrolls down. - */ - - screen_cursor_up(screen, 1, true); - - return 0; -} - -static int screen_RIS(term_screen *screen, const term_seq *seq) { - /* - * RIS - reset-to-initial-state - * This control function causes a nonvolatile memory (NVR) recall to - * occur. RIS replaces all set-up features with their saved settings. - * - * The terminal stores these saved settings in NVR memory. The saved - * setting for a feature is the same as the factory-default setting, - * unless you saved a new setting. - */ - - term_screen_hard_reset(screen); - - return 0; -} - -static int screen_RM_ANSI(term_screen *screen, const term_seq *seq) { - /* - * RM_ANSI - reset-mode-ansi - * - * TODO: implement (see VT510rm manual) - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_ansi(screen, seq->args[i], false); - - return 0; -} - -static int screen_RM_DEC(term_screen *screen, const term_seq *seq) { - /* - * RM_DEC - reset-mode-dec - * This is the same as RM_ANSI but for DEC modes. - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_dec(screen, seq->args[i], false); - - return 0; -} - -static int screen_S7C1T(term_screen *screen, const term_seq *seq) { - /* - * S7C1T - set-7bit-c1-terminal - * This causes the terminal to start sending C1 controls as 7bit - * sequences instead of 8bit C1 controls. - * This is ignored if the terminal is below level-2 emulation mode - * (VT100 and below), the terminal already sends 7bit controls then. - */ - - if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100) - screen->flags |= TERM_FLAG_7BIT_MODE; - - return 0; -} - -static int screen_S8C1T(term_screen *screen, const term_seq *seq) { - /* - * S8C1T - set-8bit-c1-terminal - * This causes the terminal to start sending C1 controls as 8bit C1 - * control instead of 7bit sequences. - * This is ignored if the terminal is below level-2 emulation mode - * (VT100 and below). The terminal always sends 7bit controls in those - * modes. - */ - - if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100) - screen->flags &= ~TERM_FLAG_7BIT_MODE; - - return 0; -} - -static int screen_SCS(term_screen *screen, const term_seq *seq) { - /* - * SCS - select-character-set - * Designate character sets to G-sets. The mapping from intermediates - * and terminal characters in the escape sequence to G-sets and - * character-sets is non-trivial and implemented separately. See there - * for more information. - * This call simply sets the selected G-set to the desired - * character-set. - */ - - term_charset *cs = NULL; - - /* TODO: support more of them? */ - switch (seq->charset) { - case TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL: - case TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL: - case TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL: - case TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL: - case TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL: - case TERM_CHARSET_ISO_LATIN_CYRILLIC: - break; - - case TERM_CHARSET_DEC_SPECIAL_GRAPHIC: - cs = &term_dec_special_graphics; - break; - case TERM_CHARSET_DEC_SUPPLEMENTAL: - cs = &term_dec_supplemental_graphics; - break; - case TERM_CHARSET_DEC_TECHNICAL: - case TERM_CHARSET_CYRILLIC_DEC: - case TERM_CHARSET_DUTCH_NRCS: - case TERM_CHARSET_FINNISH_NRCS: - case TERM_CHARSET_FRENCH_NRCS: - case TERM_CHARSET_FRENCH_CANADIAN_NRCS: - case TERM_CHARSET_GERMAN_NRCS: - case TERM_CHARSET_GREEK_DEC: - case TERM_CHARSET_GREEK_NRCS: - case TERM_CHARSET_HEBREW_DEC: - case TERM_CHARSET_HEBREW_NRCS: - case TERM_CHARSET_ITALIAN_NRCS: - case TERM_CHARSET_NORWEGIAN_DANISH_NRCS: - case TERM_CHARSET_PORTUGUESE_NRCS: - case TERM_CHARSET_RUSSIAN_NRCS: - case TERM_CHARSET_SCS_NRCS: - case TERM_CHARSET_SPANISH_NRCS: - case TERM_CHARSET_SWEDISH_NRCS: - case TERM_CHARSET_SWISS_NRCS: - case TERM_CHARSET_TURKISH_DEC: - case TERM_CHARSET_TURKISH_NRCS: - break; - - case TERM_CHARSET_USERPREF_SUPPLEMENTAL: - break; - } - - if (seq->intermediates & TERM_SEQ_FLAG_POPEN) - screen->g0 = cs ? : &term_unicode_lower; - else if (seq->intermediates & TERM_SEQ_FLAG_PCLOSE) - screen->g1 = cs ? : &term_unicode_upper; - else if (seq->intermediates & TERM_SEQ_FLAG_MULT) - screen->g2 = cs ? : &term_unicode_lower; - else if (seq->intermediates & TERM_SEQ_FLAG_PLUS) - screen->g3 = cs ? : &term_unicode_upper; - else if (seq->intermediates & TERM_SEQ_FLAG_MINUS) - screen->g1 = cs ? : &term_unicode_upper; - else if (seq->intermediates & TERM_SEQ_FLAG_DOT) - screen->g2 = cs ? : &term_unicode_lower; - else if (seq->intermediates & TERM_SEQ_FLAG_SLASH) - screen->g3 = cs ? : &term_unicode_upper; - - return 0; -} - -static int screen_SD(term_screen *screen, const term_seq *seq) { - /* - * SD - scroll-down - * This control function moves the user window down a specified number - * of lines in page memory. - * @args[0] is the number of lines to move the - * user window up in page memory. New lines appear at the top of the - * display. Old lines disappear at the bottom of the display. You - * cannot pan past the top margin of the current page. 0 is treated - * as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_scroll_down(screen->page, num, &screen->state.attr, screen->age, NULL); - - return 0; -} - -static int screen_SGR(term_screen *screen, const term_seq *seq) { - /* - * SGR - select-graphics-rendition - */ - - term_color *dst; - unsigned int i, code; - int v; - - if (seq->n_args < 1) { - zero(screen->state.attr); - return 0; - } - - for (i = 0; i < seq->n_args; ++i) { - v = seq->args[i]; - switch (v) { - case 1: - screen->state.attr.bold = 1; - break; - case 3: - screen->state.attr.italic = 1; - break; - case 4: - screen->state.attr.underline = 1; - break; - case 5: - screen->state.attr.blink = 1; - break; - case 7: - screen->state.attr.inverse = 1; - break; - case 8: - screen->state.attr.hidden = 1; - break; - case 22: - screen->state.attr.bold = 0; - break; - case 23: - screen->state.attr.italic = 0; - break; - case 24: - screen->state.attr.underline = 0; - break; - case 25: - screen->state.attr.blink = 0; - break; - case 27: - screen->state.attr.inverse = 0; - break; - case 28: - screen->state.attr.hidden = 0; - break; - case 30 ... 37: - screen->state.attr.fg.ccode = v - 30 + TERM_CCODE_BLACK; - break; - case 39: - screen->state.attr.fg.ccode = 0; - break; - case 40 ... 47: - screen->state.attr.bg.ccode = v - 40 + TERM_CCODE_BLACK; - break; - case 49: - screen->state.attr.bg.ccode = 0; - break; - case 90 ... 97: - screen->state.attr.fg.ccode = v - 90 + TERM_CCODE_LIGHT_BLACK; - break; - case 100 ... 107: - screen->state.attr.bg.ccode = v - 100 + TERM_CCODE_LIGHT_BLACK; - break; - case 38: - /* fallthrough */ - case 48: - - if (v == 38) - dst = &screen->state.attr.fg; - else - dst = &screen->state.attr.bg; - - ++i; - if (i >= seq->n_args) - break; - - switch (seq->args[i]) { - case 2: - /* 24bit-color support */ - - i += 3; - if (i >= seq->n_args) - break; - - dst->ccode = TERM_CCODE_RGB; - dst->red = (seq->args[i - 2] >= 0) ? seq->args[i - 2] : 0; - dst->green = (seq->args[i - 1] >= 0) ? seq->args[i - 1] : 0; - dst->blue = (seq->args[i] >= 0) ? seq->args[i] : 0; - - break; - case 5: - /* 256-color support */ - - ++i; - if (i >= seq->n_args || seq->args[i] < 0) - break; - - dst->ccode = TERM_CCODE_256; - code = seq->args[i]; - dst->c256 = code < 256 ? code : 0; - - break; - } - - break; - case -1: - /* fallthrough */ - case 0: - zero(screen->state.attr); - break; - } - } - - return 0; -} - -static int screen_SI(term_screen *screen, const term_seq *seq) { - /* - * SI - shift-in - * Map G0 into GL. - */ - - screen->state.gl = &screen->g0; - - return 0; -} - -static int screen_SM_ANSI(term_screen *screen, const term_seq *seq) { - /* - * SM_ANSI - set-mode-ansi - * - * TODO: implement - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_ansi(screen, seq->args[i], true); - - return 0; -} - -static int screen_SM_DEC(term_screen *screen, const term_seq *seq) { - /* - * SM_DEC - set-mode-dec - * This is the same as SM_ANSI but for DEC modes. - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_dec(screen, seq->args[i], true); - - return 0; -} - -static int screen_SO(term_screen *screen, const term_seq *seq) { - /* - * SO - shift-out - * Map G1 into GL. - */ - - screen->state.gl = &screen->g1; - - return 0; -} - -static int screen_SPA(term_screen *screen, const term_seq *seq) { - /* - * SPA - start-of-protected-area - * - * TODO: What is this? - */ - - return 0; -} - -static int screen_SS2(term_screen *screen, const term_seq *seq) { - /* - * SS2 - single-shift-2 - * Temporarily map G2 into GL for the next graphics character. - */ - - screen->state.glt = &screen->g2; - - return 0; -} - -static int screen_SS3(term_screen *screen, const term_seq *seq) { - /* - * SS3 - single-shift-3 - * Temporarily map G3 into GL for the next graphics character - */ - - screen->state.glt = &screen->g3; - - return 0; -} - -static int screen_ST(term_screen *screen, const term_seq *seq) { - /* - * ST - string-terminator - * The string-terminator is usually part of control-sequences and - * handled by the parser. In all other situations it is silently - * ignored. - */ - - return 0; -} - -static int screen_SU(term_screen *screen, const term_seq *seq) { - /* - * SU - scroll-up - * This control function moves the user window up a specified number of - * lines in page memory. - * @args[0] is the number of lines to move the - * user window down in page memory. New lines appear at the bottom of - * the display. Old lines disappear at the top of the display. You - * cannot pan past the bottom margin of the current page. 0 is treated - * as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_scroll_up(screen->page, num, &screen->state.attr, screen->age, screen->history); - - return 0; -} - -static int screen_SUB(term_screen *screen, const term_seq *seq) { - /* - * SUB - substitute - * Cancel the current control-sequence and print a replacement - * character. Our parser already handles this so all we have to do is - * print the replacement character. - */ - - static const term_seq rep = { - .type = TERM_SEQ_GRAPHIC, - .command = TERM_CMD_GRAPHIC, - .terminator = 0xfffd, - }; - - return screen_GRAPHIC(screen, &rep); -} - -static int screen_TBC(term_screen *screen, const term_seq *seq) { - /* - * TBC - tab-clear - * This clears tab-stops. If @args[0] is 0, the tab-stop at the current - * cursor position is cleared. If it is 3, all tab stops are cleared. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0, pos; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - pos = screen->state.cursor_x; - if (screen->page->width > 0) - screen->tabs[pos / 8] &= ~(1U << (pos % 8)); - break; - case 3: - if (screen->page->width > 0) - memzero(screen->tabs, (screen->page->width + 7) / 8); - break; - } - - return 0; -} - -static int screen_VPA(term_screen *screen, const term_seq *seq) { - /* - * VPA - vertical-line-position-absolute - * VPA causes the active position to be moved to the corresponding - * horizontal position. @args[0] specifies the line to jump to. If an - * attempt is made to move the active position below the last line, then - * the active position stops on the last line. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int pos = 1; - - if (seq->args[0] > 0) - pos = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set_rel(screen, screen->state.cursor_x, pos - 1); - - return 0; -} - -static int screen_VPR(term_screen *screen, const term_seq *seq) { - /* - * VPR - vertical-line-position-relative - * VPR causes the active position to be moved to the corresponding - * horizontal position. @args[0] specifies the number of lines to jump - * down relative to the current cursor position. If an attempt is made - * to move the active position below the last line, the active position - * stops at the last line. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, num, false); - - return 0; -} - -static int screen_VT(term_screen *screen, const term_seq *seq) { - /* - * VT - vertical-tab - * This causes a vertical jump by one line. Terminals treat it exactly - * the same as LF. - */ - - return screen_LF(screen, seq); -} - -static int screen_XTERM_CLLHP(term_screen *screen, const term_seq *seq) { - /* - * XTERM_CLLHP - xterm-cursor-lower-left-hp-bugfix - * Move the cursor to the lower-left corner of the page. This is an HP - * bugfix by xterm. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_IHMT(term_screen *screen, const term_seq *seq) { - /* - * XTERM_IHMT - xterm-initiate-highlight-mouse-tracking - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_MLHP(term_screen *screen, const term_seq *seq) { - /* - * XTERM_MLHP - xterm-memory-lock-hp-bugfix - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_MUHP(term_screen *screen, const term_seq *seq) { - /* - * XTERM_MUHP - xterm-memory-unlock-hp-bugfix - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_RPM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_RPM - xterm-restore-private-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_RRV(term_screen *screen, const term_seq *seq) { - /* - * XTERM_RRV - xterm-reset-resource-value - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_RTM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_RTM - xterm-reset-title-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SACL1(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SACL1 - xterm-set-ansi-conformance-level-1 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SACL2(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SACL2 - xterm-set-ansi-conformance-level-2 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SACL3(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SACL3 - xterm-set-ansi-conformance-level-3 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SDCS(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SDCS - xterm-set-default-character-set - * Select the default character set. We treat this the same as UTF-8 as - * this is our default character set. As we always use UTF-8, this - * becomes as no-op. - */ - - return 0; -} - -static int screen_XTERM_SGFX(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SGFX - xterm-sixel-graphics - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SPM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SPM - xterm-set-private-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SRV(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SRV - xterm-set-resource-value - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_STM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_STM - xterm-set-title-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SUCS(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SUCS - xterm-select-utf8-character-set - * Select UTF-8 as character set. This is our default on only character - * set. Hence, this is a no-op. - */ - - return 0; -} - -static int screen_XTERM_WM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_WM - xterm-window-management - * - * Probably not worth implementing. - */ - - return 0; -} - -/* - * Feeding data - * The screen_feed_*() handlers take data from the user and feed it into the - * screen. Once the parser has detected a sequence, we parse the command-type - * and forward it to the command-dispatchers. - */ - -static int screen_feed_cmd(term_screen *screen, const term_seq *seq) { - switch (seq->command) { - case TERM_CMD_GRAPHIC: - return screen_GRAPHIC(screen, seq); - case TERM_CMD_BEL: - return screen_BEL(screen, seq); - case TERM_CMD_BS: - return screen_BS(screen, seq); - case TERM_CMD_CBT: - return screen_CBT(screen, seq); - case TERM_CMD_CHA: - return screen_CHA(screen, seq); - case TERM_CMD_CHT: - return screen_CHT(screen, seq); - case TERM_CMD_CNL: - return screen_CNL(screen, seq); - case TERM_CMD_CPL: - return screen_CPL(screen, seq); - case TERM_CMD_CR: - return screen_CR(screen, seq); - case TERM_CMD_CUB: - return screen_CUB(screen, seq); - case TERM_CMD_CUD: - return screen_CUD(screen, seq); - case TERM_CMD_CUF: - return screen_CUF(screen, seq); - case TERM_CMD_CUP: - return screen_CUP(screen, seq); - case TERM_CMD_CUU: - return screen_CUU(screen, seq); - case TERM_CMD_DA1: - return screen_DA1(screen, seq); - case TERM_CMD_DA2: - return screen_DA2(screen, seq); - case TERM_CMD_DA3: - return screen_DA3(screen, seq); - case TERM_CMD_DC1: - return screen_DC1(screen, seq); - case TERM_CMD_DC3: - return screen_DC3(screen, seq); - case TERM_CMD_DCH: - return screen_DCH(screen, seq); - case TERM_CMD_DECALN: - return screen_DECALN(screen, seq); - case TERM_CMD_DECANM: - return screen_DECANM(screen, seq); - case TERM_CMD_DECBI: - return screen_DECBI(screen, seq); - case TERM_CMD_DECCARA: - return screen_DECCARA(screen, seq); - case TERM_CMD_DECCRA: - return screen_DECCRA(screen, seq); - case TERM_CMD_DECDC: - return screen_DECDC(screen, seq); - case TERM_CMD_DECDHL_BH: - return screen_DECDHL_BH(screen, seq); - case TERM_CMD_DECDHL_TH: - return screen_DECDHL_TH(screen, seq); - case TERM_CMD_DECDWL: - return screen_DECDWL(screen, seq); - case TERM_CMD_DECEFR: - return screen_DECEFR(screen, seq); - case TERM_CMD_DECELF: - return screen_DECELF(screen, seq); - case TERM_CMD_DECELR: - return screen_DECELR(screen, seq); - case TERM_CMD_DECERA: - return screen_DECERA(screen, seq); - case TERM_CMD_DECFI: - return screen_DECFI(screen, seq); - case TERM_CMD_DECFRA: - return screen_DECFRA(screen, seq); - case TERM_CMD_DECIC: - return screen_DECIC(screen, seq); - case TERM_CMD_DECID: - return screen_DECID(screen, seq); - case TERM_CMD_DECINVM: - return screen_DECINVM(screen, seq); - case TERM_CMD_DECKBD: - return screen_DECKBD(screen, seq); - case TERM_CMD_DECKPAM: - return screen_DECKPAM(screen, seq); - case TERM_CMD_DECKPNM: - return screen_DECKPNM(screen, seq); - case TERM_CMD_DECLFKC: - return screen_DECLFKC(screen, seq); - case TERM_CMD_DECLL: - return screen_DECLL(screen, seq); - case TERM_CMD_DECLTOD: - return screen_DECLTOD(screen, seq); - case TERM_CMD_DECPCTERM: - return screen_DECPCTERM(screen, seq); - case TERM_CMD_DECPKA: - return screen_DECPKA(screen, seq); - case TERM_CMD_DECPKFMR: - return screen_DECPKFMR(screen, seq); - case TERM_CMD_DECRARA: - return screen_DECRARA(screen, seq); - case TERM_CMD_DECRC: - return screen_DECRC(screen, seq); - case TERM_CMD_DECREQTPARM: - return screen_DECREQTPARM(screen, seq); - case TERM_CMD_DECRPKT: - return screen_DECRPKT(screen, seq); - case TERM_CMD_DECRQCRA: - return screen_DECRQCRA(screen, seq); - case TERM_CMD_DECRQDE: - return screen_DECRQDE(screen, seq); - case TERM_CMD_DECRQKT: - return screen_DECRQKT(screen, seq); - case TERM_CMD_DECRQLP: - return screen_DECRQLP(screen, seq); - case TERM_CMD_DECRQM_ANSI: - return screen_DECRQM_ANSI(screen, seq); - case TERM_CMD_DECRQM_DEC: - return screen_DECRQM_DEC(screen, seq); - case TERM_CMD_DECRQPKFM: - return screen_DECRQPKFM(screen, seq); - case TERM_CMD_DECRQPSR: - return screen_DECRQPSR(screen, seq); - case TERM_CMD_DECRQTSR: - return screen_DECRQTSR(screen, seq); - case TERM_CMD_DECRQUPSS: - return screen_DECRQUPSS(screen, seq); - case TERM_CMD_DECSACE: - return screen_DECSACE(screen, seq); - case TERM_CMD_DECSASD: - return screen_DECSASD(screen, seq); - case TERM_CMD_DECSC: - return screen_DECSC(screen, seq); - case TERM_CMD_DECSCA: - return screen_DECSCA(screen, seq); - case TERM_CMD_DECSCL: - return screen_DECSCL(screen, seq); - case TERM_CMD_DECSCP: - return screen_DECSCP(screen, seq); - case TERM_CMD_DECSCPP: - return screen_DECSCPP(screen, seq); - case TERM_CMD_DECSCS: - return screen_DECSCS(screen, seq); - case TERM_CMD_DECSCUSR: - return screen_DECSCUSR(screen, seq); - case TERM_CMD_DECSDDT: - return screen_DECSDDT(screen, seq); - case TERM_CMD_DECSDPT: - return screen_DECSDPT(screen, seq); - case TERM_CMD_DECSED: - return screen_DECSED(screen, seq); - case TERM_CMD_DECSEL: - return screen_DECSEL(screen, seq); - case TERM_CMD_DECSERA: - return screen_DECSERA(screen, seq); - case TERM_CMD_DECSFC: - return screen_DECSFC(screen, seq); - case TERM_CMD_DECSKCV: - return screen_DECSKCV(screen, seq); - case TERM_CMD_DECSLCK: - return screen_DECSLCK(screen, seq); - case TERM_CMD_DECSLE: - return screen_DECSLE(screen, seq); - case TERM_CMD_DECSLPP: - return screen_DECSLPP(screen, seq); - case TERM_CMD_DECSLRM_OR_SC: - return screen_DECSLRM_OR_SC(screen, seq); - case TERM_CMD_DECSMBV: - return screen_DECSMBV(screen, seq); - case TERM_CMD_DECSMKR: - return screen_DECSMKR(screen, seq); - case TERM_CMD_DECSNLS: - return screen_DECSNLS(screen, seq); - case TERM_CMD_DECSPP: - return screen_DECSPP(screen, seq); - case TERM_CMD_DECSPPCS: - return screen_DECSPPCS(screen, seq); - case TERM_CMD_DECSPRTT: - return screen_DECSPRTT(screen, seq); - case TERM_CMD_DECSR: - return screen_DECSR(screen, seq); - case TERM_CMD_DECSRFR: - return screen_DECSRFR(screen, seq); - case TERM_CMD_DECSSCLS: - return screen_DECSSCLS(screen, seq); - case TERM_CMD_DECSSDT: - return screen_DECSSDT(screen, seq); - case TERM_CMD_DECSSL: - return screen_DECSSL(screen, seq); - case TERM_CMD_DECST8C: - return screen_DECST8C(screen, seq); - case TERM_CMD_DECSTBM: - return screen_DECSTBM(screen, seq); - case TERM_CMD_DECSTR: - return screen_DECSTR(screen, seq); - case TERM_CMD_DECSTRL: - return screen_DECSTRL(screen, seq); - case TERM_CMD_DECSWBV: - return screen_DECSWBV(screen, seq); - case TERM_CMD_DECSWL: - return screen_DECSWL(screen, seq); - case TERM_CMD_DECTID: - return screen_DECTID(screen, seq); - case TERM_CMD_DECTME: - return screen_DECTME(screen, seq); - case TERM_CMD_DECTST: - return screen_DECTST(screen, seq); - case TERM_CMD_DL: - return screen_DL(screen, seq); - case TERM_CMD_DSR_ANSI: - return screen_DSR_ANSI(screen, seq); - case TERM_CMD_DSR_DEC: - return screen_DSR_DEC(screen, seq); - case TERM_CMD_ECH: - return screen_ECH(screen, seq); - case TERM_CMD_ED: - return screen_ED(screen, seq); - case TERM_CMD_EL: - return screen_EL(screen, seq); - case TERM_CMD_ENQ: - return screen_ENQ(screen, seq); - case TERM_CMD_EPA: - return screen_EPA(screen, seq); - case TERM_CMD_FF: - return screen_FF(screen, seq); - case TERM_CMD_HPA: - return screen_HPA(screen, seq); - case TERM_CMD_HPR: - return screen_HPR(screen, seq); - case TERM_CMD_HT: - return screen_HT(screen, seq); - case TERM_CMD_HTS: - return screen_HTS(screen, seq); - case TERM_CMD_HVP: - return screen_HVP(screen, seq); - case TERM_CMD_ICH: - return screen_ICH(screen, seq); - case TERM_CMD_IL: - return screen_IL(screen, seq); - case TERM_CMD_IND: - return screen_IND(screen, seq); - case TERM_CMD_LF: - return screen_LF(screen, seq); - case TERM_CMD_LS1R: - return screen_LS1R(screen, seq); - case TERM_CMD_LS2: - return screen_LS2(screen, seq); - case TERM_CMD_LS2R: - return screen_LS2R(screen, seq); - case TERM_CMD_LS3: - return screen_LS3(screen, seq); - case TERM_CMD_LS3R: - return screen_LS3R(screen, seq); - case TERM_CMD_MC_ANSI: - return screen_MC_ANSI(screen, seq); - case TERM_CMD_MC_DEC: - return screen_MC_DEC(screen, seq); - case TERM_CMD_NEL: - return screen_NEL(screen, seq); - case TERM_CMD_NP: - return screen_NP(screen, seq); - case TERM_CMD_NULL: - return screen_NULL(screen, seq); - case TERM_CMD_PP: - return screen_PP(screen, seq); - case TERM_CMD_PPA: - return screen_PPA(screen, seq); - case TERM_CMD_PPB: - return screen_PPB(screen, seq); - case TERM_CMD_PPR: - return screen_PPR(screen, seq); - case TERM_CMD_RC: - return screen_RC(screen, seq); - case TERM_CMD_REP: - return screen_REP(screen, seq); - case TERM_CMD_RI: - return screen_RI(screen, seq); - case TERM_CMD_RIS: - return screen_RIS(screen, seq); - case TERM_CMD_RM_ANSI: - return screen_RM_ANSI(screen, seq); - case TERM_CMD_RM_DEC: - return screen_RM_DEC(screen, seq); - case TERM_CMD_S7C1T: - return screen_S7C1T(screen, seq); - case TERM_CMD_S8C1T: - return screen_S8C1T(screen, seq); - case TERM_CMD_SCS: - return screen_SCS(screen, seq); - case TERM_CMD_SD: - return screen_SD(screen, seq); - case TERM_CMD_SGR: - return screen_SGR(screen, seq); - case TERM_CMD_SI: - return screen_SI(screen, seq); - case TERM_CMD_SM_ANSI: - return screen_SM_ANSI(screen, seq); - case TERM_CMD_SM_DEC: - return screen_SM_DEC(screen, seq); - case TERM_CMD_SO: - return screen_SO(screen, seq); - case TERM_CMD_SPA: - return screen_SPA(screen, seq); - case TERM_CMD_SS2: - return screen_SS2(screen, seq); - case TERM_CMD_SS3: - return screen_SS3(screen, seq); - case TERM_CMD_ST: - return screen_ST(screen, seq); - case TERM_CMD_SU: - return screen_SU(screen, seq); - case TERM_CMD_SUB: - return screen_SUB(screen, seq); - case TERM_CMD_TBC: - return screen_TBC(screen, seq); - case TERM_CMD_VPA: - return screen_VPA(screen, seq); - case TERM_CMD_VPR: - return screen_VPR(screen, seq); - case TERM_CMD_VT: - return screen_VT(screen, seq); - case TERM_CMD_XTERM_CLLHP: - return screen_XTERM_CLLHP(screen, seq); - case TERM_CMD_XTERM_IHMT: - return screen_XTERM_IHMT(screen, seq); - case TERM_CMD_XTERM_MLHP: - return screen_XTERM_MLHP(screen, seq); - case TERM_CMD_XTERM_MUHP: - return screen_XTERM_MUHP(screen, seq); - case TERM_CMD_XTERM_RPM: - return screen_XTERM_RPM(screen, seq); - case TERM_CMD_XTERM_RRV: - return screen_XTERM_RRV(screen, seq); - case TERM_CMD_XTERM_RTM: - return screen_XTERM_RTM(screen, seq); - case TERM_CMD_XTERM_SACL1: - return screen_XTERM_SACL1(screen, seq); - case TERM_CMD_XTERM_SACL2: - return screen_XTERM_SACL2(screen, seq); - case TERM_CMD_XTERM_SACL3: - return screen_XTERM_SACL3(screen, seq); - case TERM_CMD_XTERM_SDCS: - return screen_XTERM_SDCS(screen, seq); - case TERM_CMD_XTERM_SGFX: - return screen_XTERM_SGFX(screen, seq); - case TERM_CMD_XTERM_SPM: - return screen_XTERM_SPM(screen, seq); - case TERM_CMD_XTERM_SRV: - return screen_XTERM_SRV(screen, seq); - case TERM_CMD_XTERM_STM: - return screen_XTERM_STM(screen, seq); - case TERM_CMD_XTERM_SUCS: - return screen_XTERM_SUCS(screen, seq); - case TERM_CMD_XTERM_WM: - return screen_XTERM_WM(screen, seq); - } - - return 0; -} - -unsigned int term_screen_get_width(term_screen *screen) { - assert_return(screen, -EINVAL); - - return screen->page->width; -} - -unsigned int term_screen_get_height(term_screen *screen) { - assert_return(screen, -EINVAL); - - return screen->page->height; -} - -uint64_t term_screen_get_age(term_screen *screen) { - assert_return(screen, 0); - - return screen->age; -} - -int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) { - uint32_t *ucs4_str; - size_t i, j, ucs4_len; - const term_seq *seq; - int r; - - assert_return(screen, -EINVAL); - - ++screen->age; - - /* Feed bytes into utf8 decoder and handle parsed ucs4 chars. We always - * treat data as UTF-8, but the parser makes sure to fall back to raw - * 8bit mode if the stream is not valid UTF-8. This should be more than - * enough to support old 7bit/8bit modes. */ - for (i = 0; i < size; ++i) { - ucs4_len = term_utf8_decode(&screen->utf8, &ucs4_str, in[i]); - for (j = 0; j < ucs4_len; ++j) { - r = term_parser_feed(screen->parser, &seq, ucs4_str[j]); - if (r < 0) { - return r; - } else if (r != TERM_SEQ_NONE) { - r = screen_feed_cmd(screen, seq); - if (r < 0) - return r; - } - } - } - - return 0; -} - -static char *screen_map_key(term_screen *screen, - char *p, - const uint32_t *keysyms, - size_t n_syms, - uint32_t ascii, - const uint32_t *ucs4, - unsigned int mods) { - char ch, ch2, ch_mods; - uint32_t v; - size_t i; - - /* TODO: All these key-mappings need to be verified. Public information - * on those mappings is pretty scarce and every emulator seems to do it - * slightly differently. - * A lot of mappings are also missing. */ - - if (n_syms < 1) - return p; - - if (n_syms == 1) - v = keysyms[0]; - else - v = XKB_KEY_NoSymbol; - - /* In some mappings, the modifiers are encoded as CSI parameters. The - * encoding is rather arbitrary, but seems to work. */ - ch_mods = 0; - switch (mods & (TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT | TERM_KBDMOD_CTRL)) { - case TERM_KBDMOD_SHIFT: - ch_mods = '2'; - break; - case TERM_KBDMOD_ALT: - ch_mods = '3'; - break; - case TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT: - ch_mods = '4'; - break; - case TERM_KBDMOD_CTRL: - ch_mods = '5'; - break; - case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT: - ch_mods = '6'; - break; - case TERM_KBDMOD_CTRL | TERM_KBDMOD_ALT: - ch_mods = '7'; - break; - case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT: - ch_mods = '8'; - break; - } - - /* A user might actually use multiple layouts for keyboard - * input. @keysyms[0] contains the actual keysym that the user - * used. But if this keysym is not in the ascii range, the - * input handler does check all other layouts that the user - * specified whether one of them maps the key to some ASCII - * keysym and provides this via @ascii. We always use the real - * keysym except when handling CTRL+<XY> shortcuts we use the - * ascii keysym. This is for compatibility to xterm et. al. so - * ctrl+c always works regardless of the currently active - * keyboard layout. But if no ascii-sym is found, we still use - * the real keysym. */ - if (ascii == XKB_KEY_NoSymbol) - ascii = v; - - /* map CTRL+<ascii> */ - if (mods & TERM_KBDMOD_CTRL) { - switch (ascii) { - case 0x60 ... 0x7e: - /* Right hand side is mapped to the left and then - * treated equally. Fall through to left-hand side.. */ - ascii -= 0x20; - case 0x20 ... 0x5f: - /* Printable ASCII is mapped 1-1 in XKB and in - * combination with CTRL bit 7 is flipped. This - * is equivalent to the caret-notation. */ - *p++ = ascii ^ 0x40; - return p; - } - } - - /* map cursor keys */ - ch = 0; - switch (v) { - case XKB_KEY_Up: - ch = 'A'; - break; - case XKB_KEY_Down: - ch = 'B'; - break; - case XKB_KEY_Right: - ch = 'C'; - break; - case XKB_KEY_Left: - ch = 'D'; - break; - case XKB_KEY_Home: - ch = 'H'; - break; - case XKB_KEY_End: - ch = 'F'; - break; - } - if (ch) { - *p++ = 0x1b; - if (screen->flags & TERM_FLAG_CURSOR_KEYS) - *p++ = 'O'; - else - *p++ = '['; - if (ch_mods) { - *p++ = '1'; - *p++ = ';'; - *p++ = ch_mods; - } - *p++ = ch; - return p; - } - - /* map action keys */ - ch = 0; - switch (v) { - case XKB_KEY_Find: - ch = '1'; - break; - case XKB_KEY_Insert: - ch = '2'; - break; - case XKB_KEY_Delete: - ch = '3'; - break; - case XKB_KEY_Select: - ch = '4'; - break; - case XKB_KEY_Page_Up: - ch = '5'; - break; - case XKB_KEY_Page_Down: - ch = '6'; - break; - } - if (ch) { - *p++ = 0x1b; - *p++ = '['; - *p++ = ch; - if (ch_mods) { - *p++ = ';'; - *p++ = ch_mods; - } - *p++ = '~'; - return p; - } - - /* map lower function keys */ - ch = 0; - switch (v) { - case XKB_KEY_F1: - ch = 'P'; - break; - case XKB_KEY_F2: - ch = 'Q'; - break; - case XKB_KEY_F3: - ch = 'R'; - break; - case XKB_KEY_F4: - ch = 'S'; - break; - } - if (ch) { - if (ch_mods) { - *p++ = 0x1b; - *p++ = '['; - *p++ = '1'; - *p++ = ';'; - *p++ = ch_mods; - *p++ = ch; - } else { - *p++ = 0x1b; - *p++ = 'O'; - *p++ = ch; - } - - return p; - } - - /* map upper function keys */ - ch = 0; - ch2 = 0; - switch (v) { - case XKB_KEY_F5: - ch = '1'; - ch2 = '5'; - break; - case XKB_KEY_F6: - ch = '1'; - ch2 = '7'; - break; - case XKB_KEY_F7: - ch = '1'; - ch2 = '8'; - break; - case XKB_KEY_F8: - ch = '1'; - ch2 = '9'; - break; - case XKB_KEY_F9: - ch = '2'; - ch2 = '0'; - break; - case XKB_KEY_F10: - ch = '2'; - ch2 = '1'; - break; - case XKB_KEY_F11: - ch = '2'; - ch2 = '2'; - break; - case XKB_KEY_F12: - ch = '2'; - ch2 = '3'; - break; - } - if (ch) { - *p++ = 0x1b; - *p++ = '['; - *p++ = ch; - if (ch2) - *p++ = ch2; - if (ch_mods) { - *p++ = ';'; - *p++ = ch_mods; - } - *p++ = '~'; - return p; - } - - /* map special keys */ - switch (v) { - case 0xff08: /* XKB_KEY_BackSpace */ - case 0xff09: /* XKB_KEY_Tab */ - case 0xff0a: /* XKB_KEY_Linefeed */ - case 0xff0b: /* XKB_KEY_Clear */ - case 0xff15: /* XKB_KEY_Sys_Req */ - case 0xff1b: /* XKB_KEY_Escape */ - case 0xffff: /* XKB_KEY_Delete */ - *p++ = v - 0xff00; - return p; - case 0xff13: /* XKB_KEY_Pause */ - /* TODO: What should we do with this key? - * Sending XOFF is awful as there is no simple - * way on modern keyboards to send XON again. - * If someone wants this, we can re-eanble - * optionally. */ - return p; - case 0xff14: /* XKB_KEY_Scroll_Lock */ - /* TODO: What should we do on scroll-lock? - * Sending 0x14 is what the specs say but it is - * not used today the way most users would - * expect so we disable it. If someone wants - * this, we can re-enable it (optionally). */ - return p; - case XKB_KEY_Return: - *p++ = 0x0d; - if (screen->flags & TERM_FLAG_NEWLINE_MODE) - *p++ = 0x0a; - return p; - case XKB_KEY_ISO_Left_Tab: - *p++ = 0x09; - return p; - } - - /* map unicode keys */ - for (i = 0; i < n_syms; ++i) - p += utf8_encode_unichar(p, ucs4[i]); - - return p; -} - -int term_screen_feed_keyboard(term_screen *screen, - const uint32_t *keysyms, - size_t n_syms, - uint32_t ascii, - const uint32_t *ucs4, - unsigned int mods) { - _cleanup_free_ char *dyn = NULL; - static const size_t padding = 1; - char buf[128], *start, *p; - - assert_return(screen, -EINVAL); - - /* allocate buffer if too small */ - start = buf; - if (4 * n_syms + padding > sizeof(buf)) { - dyn = malloc(4 * n_syms + padding); - if (!dyn) - return -ENOMEM; - - start = dyn; - } - - /* reserve prefix space */ - start += padding; - p = start; - - p = screen_map_key(screen, p, keysyms, n_syms, ascii, ucs4, mods); - if (!p || p - start < 1) - return 0; - - /* The ALT modifier causes ESC to be prepended to any key-stroke. We - * already accounted for that buffer space above, so simply prepend it - * here. - * TODO: is altSendsEscape a suitable default? What are the semantics - * exactly? Is it used in C0/C1 conversion? Is it prepended if there - * already is an escape character? */ - if (mods & TERM_KBDMOD_ALT && *start != 0x1b) - *--start = 0x1b; - - /* turn C0 into C1 */ - if (!(screen->flags & TERM_FLAG_7BIT_MODE) && p - start >= 2) - if (start[0] == 0x1b && start[1] >= 0x40 && start[1] <= 0x5f) - *++start ^= 0x40; - - return screen_write(screen, start, p - start); -} - -int term_screen_resize(term_screen *screen, unsigned int x, unsigned int y) { - unsigned int i; - uint8_t *t; - int r; - - assert_return(screen, -EINVAL); - - r = term_page_reserve(screen->page_main, x, y, &screen->state.attr, screen->age); - if (r < 0) - return r; - - r = term_page_reserve(screen->page_alt, x, y, &screen->state.attr, screen->age); - if (r < 0) - return r; - - if (x > screen->n_tabs) { - t = realloc(screen->tabs, (x + 7) / 8); - if (!t) - return -ENOMEM; - - screen->tabs = t; - screen->n_tabs = x; - } - - for (i = (screen->page->width + 7) / 8 * 8; i < x; i += 8) - screen->tabs[i / 8] = 0x1; - - term_page_resize(screen->page_main, x, y, &screen->state.attr, screen->age, screen->history); - term_page_resize(screen->page_alt, x, y, &screen->state.attr, screen->age, NULL); - - screen->state.cursor_x = screen_clamp_x(screen, screen->state.cursor_x); - screen->state.cursor_y = screen_clamp_x(screen, screen->state.cursor_y); - screen_cursor_clear_wrap(screen); - - return 0; -} - -void term_screen_soft_reset(term_screen *screen) { - unsigned int i; - - assert(screen); - - screen->g0 = &term_unicode_lower; - screen->g1 = &term_unicode_upper; - screen->g2 = &term_unicode_lower; - screen->g3 = &term_unicode_upper; - screen->state.attr = screen->default_attr; - screen->state.gl = &screen->g0; - screen->state.gr = &screen->g1; - screen->state.glt = NULL; - screen->state.grt = NULL; - screen->state.auto_wrap = 0; - screen->state.origin_mode = 0; - - screen->saved = screen->state; - screen->saved.cursor_x = 0; - screen->saved.cursor_y = 0; - screen->saved_alt = screen->saved; - - screen->page = screen->page_main; - screen->history = screen->history_main; - screen->flags = TERM_FLAG_7BIT_MODE; - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; - - for (i = 0; i < screen->page->width; i += 8) - screen->tabs[i / 8] = 0x1; - - term_page_set_scroll_region(screen->page_main, 0, screen->page->height); - term_page_set_scroll_region(screen->page_alt, 0, screen->page->height); -} - -void term_screen_hard_reset(term_screen *screen) { - assert(screen); - - term_screen_soft_reset(screen); - zero(screen->utf8); - screen->state.cursor_x = 0; - screen->state.cursor_y = 0; - term_page_erase(screen->page_main, 0, 0, screen->page->width, screen->page->height, &screen->state.attr, screen->age, false); - term_page_erase(screen->page_alt, 0, 0, screen->page->width, screen->page->height, &screen->state.attr, screen->age, false); -} - -int term_screen_set_answerback(term_screen *screen, const char *answerback) { - char *t = NULL; - - assert_return(screen, -EINVAL); - - if (answerback) { - t = strdup(answerback); - if (!t) - return -ENOMEM; - } - - free(screen->answerback); - screen->answerback = t; - - return 0; -} - -int term_screen_draw(term_screen *screen, - int (*draw_fn) (term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width), - void *userdata, - uint64_t *fb_age) { - uint64_t cell_age, line_age, age = 0; - term_charbuf_t ch_buf; - const uint32_t *ch_str; - unsigned int i, j, cw; - term_page *page; - term_line *line; - term_cell *cell; - size_t ch_n; - int r; - - assert(screen); - assert(draw_fn); - - if (fb_age) - age = *fb_age; - - page = screen->page; - - for (j = 0; j < page->height; ++j) { - line = page->lines[j]; - line_age = MAX(line->age, page->age); - - for (i = 0; i < page->width; ++i) { - term_attr attr; - - cell = &line->cells[i]; - cell_age = MAX(cell->age, line_age); - - if (age != 0 && cell_age <= age) - continue; - - ch_str = term_char_resolve(cell->ch, &ch_n, &ch_buf); - - /* Character-width of 0 is used for cleared cells. - * Always treat this as single-cell character, so - * renderers can assume ch_width is set properpy. */ - cw = MAX(cell->cwidth, 1U); - - attr = cell->attr; - if (i == screen->state.cursor_x && j == screen->state.cursor_y && - !(screen->flags & TERM_FLAG_HIDE_CURSOR)) - attr.inverse ^= 1; - - r = draw_fn(screen, - userdata, - i, - j, - &attr, - ch_str, - ch_n, - cw); - if (r != 0) - return r; - } - } - - if (fb_age) - *fb_age = screen->age; - - return 0; -} diff --git a/src/libsystemd-terminal/term-wcwidth.c b/src/libsystemd-terminal/term-wcwidth.c deleted file mode 100644 index 833a099bd7..0000000000 --- a/src/libsystemd-terminal/term-wcwidth.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * (Minimal changes made by David Herrmann, to make clean for inclusion in - * systemd. Original header follows.) - * - * This is an implementation of wcwidth() and wcswidth() (defined in - * IEEE Std 1002.1-2001) for Unicode. - * - * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html - * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html - * - * In fixed-width output devices, Latin characters all occupy a single - * "cell" position of equal width, whereas ideographic CJK characters - * occupy two such cells. Interoperability between terminal-line - * applications and (teletype-style) character terminals using the - * UTF-8 encoding requires agreement on which character should advance - * the cursor by how many cell positions. No established formal - * standards exist at present on which Unicode character shall occupy - * how many cell positions on character terminals. These routines are - * a first attempt of defining such behavior based on simple rules - * applied to data provided by the Unicode Consortium. - * - * For some graphical characters, the Unicode standard explicitly - * defines a character-cell width via the definition of the East Asian - * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. - * In all these cases, there is no ambiguity about which width a - * terminal shall use. For characters in the East Asian Ambiguous (A) - * class, the width choice depends purely on a preference of backward - * compatibility with either historic CJK or Western practice. - * Choosing single-width for these characters is easy to justify as - * the appropriate long-term solution, as the CJK practice of - * displaying these characters as double-width comes from historic - * implementation simplicity (8-bit encoded characters were displayed - * single-width and 16-bit ones double-width, even for Greek, - * Cyrillic, etc.) and not any typographic considerations. - * - * Much less clear is the choice of width for the Not East Asian - * (Neutral) class. Existing practice does not dictate a width for any - * of these characters. It would nevertheless make sense - * typographically to allocate two character cells to characters such - * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be - * represented adequately with a single-width glyph. The following - * routines at present merely assign a single-cell width to all - * neutral characters, in the interest of simplicity. This is not - * entirely satisfactory and should be reconsidered before - * establishing a formal standard in this area. At the moment, the - * decision which Not East Asian (Neutral) characters should be - * represented by double-width glyphs cannot yet be answered by - * applying a simple rule from the Unicode database content. Setting - * up a proper standard for the behavior of UTF-8 character terminals - * will require a careful analysis not only of each Unicode character, - * but also of each presentation form, something the author of these - * routines has avoided to do so far. - * - * http://www.unicode.org/unicode/reports/tr11/ - * - * Markus Kuhn -- 2007-05-26 (Unicode 5.0) - * - * Permission to use, copy, modify, and distribute this software - * for any purpose and without fee is hereby granted. The author - * disclaims all warranties with regard to this software. - * - * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c - */ - -#include "term-internal.h" - -struct interval { - wchar_t first; - wchar_t last; -}; - -/* auxiliary function for binary search in interval table */ -static int bisearch(wchar_t ucs, const struct interval *table, int max) { - int min = 0; - int mid; - - if (ucs < table[0].first || ucs > table[max].last) - return 0; - while (max >= min) { - mid = (min + max) / 2; - if (ucs > table[mid].last) - min = mid + 1; - else if (ucs < table[mid].first) - max = mid - 1; - else - return 1; - } - - return 0; -} - - -/* The following two functions define the column width of an ISO 10646 - * character as follows: - * - * - The null character (U+0000) has a column width of 0. - * - * - Other C0/C1 control characters and DEL will lead to a return - * value of -1. - * - * - Non-spacing and enclosing combining characters (general - * category code Mn or Me in the Unicode database) have a - * column width of 0. - * - * - SOFT HYPHEN (U+00AD) has a column width of 1. - * - * - Other format characters (general category code Cf in the Unicode - * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. - * - * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) - * have a column width of 0. - * - * - Spacing characters in the East Asian Wide (W) or East Asian - * Full-width (F) category as defined in Unicode Technical - * Report #11 have a column width of 2. - * - * - All remaining characters (including all printable - * ISO 8859-1 and WGL4 characters, Unicode control characters, - * etc.) have a column width of 1. - * - * This implementation assumes that wchar_t characters are encoded - * in ISO 10646. - */ - -int mk_wcwidth(wchar_t ucs) -{ - /* sorted list of non-overlapping intervals of non-spacing characters */ - /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ - static const struct interval combining[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF } - }; - - /* test for 8-bit control characters */ - if (ucs == 0) - return 0; - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) - return -1; - - /* binary search in table of non-spacing characters */ - if (bisearch(ucs, combining, - sizeof(combining) / sizeof(struct interval) - 1)) - return 0; - - /* if we arrive here, ucs is not a combining or C0/C1 control character */ - - return 1 + - (ucs >= 0x1100 && - (ucs <= 0x115f || /* Hangul Jamo init. consonants */ - ucs == 0x2329 || ucs == 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && - ucs != 0x303f) || /* CJK ... Yi */ - (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ - (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ - (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ - (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ - (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ - (ucs >= 0xffe0 && ucs <= 0xffe6) || - (ucs >= 0x20000 && ucs <= 0x2fffd) || - (ucs >= 0x30000 && ucs <= 0x3fffd))); -} - - -int mk_wcswidth(const wchar_t *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} - - -/* - * The following functions are the same as mk_wcwidth() and - * mk_wcswidth(), except that spacing characters in the East Asian - * Ambiguous (A) category as defined in Unicode Technical Report #11 - * have a column width of 2. This variant might be useful for users of - * CJK legacy encodings who want to migrate to UCS without changing - * the traditional terminal character-width behaviour. It is not - * otherwise recommended for general use. - */ -int mk_wcwidth_cjk(wchar_t ucs) -{ - /* sorted list of non-overlapping intervals of East Asian Ambiguous - * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ - static const struct interval ambiguous[] = { - { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, - { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, - { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, - { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, - { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, - { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, - { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, - { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, - { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, - { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, - { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, - { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, - { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, - { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, - { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, - { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, - { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, - { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, - { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, - { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, - { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, - { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, - { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, - { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, - { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, - { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, - { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, - { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, - { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, - { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, - { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, - { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, - { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, - { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, - { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, - { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, - { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, - { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, - { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, - { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, - { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, - { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, - { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, - { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, - { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, - { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, - { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, - { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, - { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, - { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, - { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, - { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } - }; - - /* binary search in table of non-spacing characters */ - if (bisearch(ucs, ambiguous, - sizeof(ambiguous) / sizeof(struct interval) - 1)) - return 2; - - return mk_wcwidth(ucs); -} - - -int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth_cjk(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h deleted file mode 100644 index 1a78a81184..0000000000 --- a/src/libsystemd-terminal/term.h +++ /dev/null @@ -1,183 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include "util.h" - -typedef struct term_color term_color; -typedef struct term_attr term_attr; - -typedef struct term_utf8 term_utf8; -typedef struct term_seq term_seq; -typedef struct term_parser term_parser; - -typedef struct term_screen term_screen; - -/* - * Ageing - */ - -typedef uint64_t term_age_t; - -#define TERM_AGE_NULL 0 - -/* - * Attributes - */ - -enum { - /* special color-codes */ - TERM_CCODE_DEFAULT, /* default foreground/background color */ - TERM_CCODE_256, /* 256color code */ - TERM_CCODE_RGB, /* color is specified as RGB */ - - /* dark color-codes */ - TERM_CCODE_BLACK, - TERM_CCODE_RED, - TERM_CCODE_GREEN, - TERM_CCODE_YELLOW, - TERM_CCODE_BLUE, - TERM_CCODE_MAGENTA, - TERM_CCODE_CYAN, - TERM_CCODE_WHITE, /* technically: light grey */ - - /* light color-codes */ - TERM_CCODE_LIGHT_BLACK = TERM_CCODE_BLACK + 8, /* technically: dark grey */ - TERM_CCODE_LIGHT_RED = TERM_CCODE_RED + 8, - TERM_CCODE_LIGHT_GREEN = TERM_CCODE_GREEN + 8, - TERM_CCODE_LIGHT_YELLOW = TERM_CCODE_YELLOW + 8, - TERM_CCODE_LIGHT_BLUE = TERM_CCODE_BLUE + 8, - TERM_CCODE_LIGHT_MAGENTA = TERM_CCODE_MAGENTA + 8, - TERM_CCODE_LIGHT_CYAN = TERM_CCODE_CYAN + 8, - TERM_CCODE_LIGHT_WHITE = TERM_CCODE_WHITE + 8, - - TERM_CCODE_CNT, -}; - -struct term_color { - uint8_t ccode; - uint8_t c256; - uint8_t red; - uint8_t green; - uint8_t blue; -}; - -struct term_attr { - term_color fg; /* foreground color */ - term_color bg; /* background color */ - - unsigned int bold : 1; /* bold font */ - unsigned int italic : 1; /* italic font */ - unsigned int underline : 1; /* underline text */ - unsigned int inverse : 1; /* inverse fg/bg */ - unsigned int protect : 1; /* protect from erase */ - unsigned int blink : 1; /* blink text */ - unsigned int hidden : 1; /* hidden */ -}; - -void term_attr_to_argb32(const term_attr *attr, uint32_t *fg, uint32_t *bg, const uint8_t *palette); - -/* - * UTF-8 - */ - -struct term_utf8 { - uint32_t chars[5]; - uint32_t ucs4; - - unsigned int i_bytes : 3; - unsigned int n_bytes : 3; - unsigned int valid : 1; -}; - -size_t term_utf8_decode(term_utf8 *p, uint32_t **out_buf, char c); - -/* - * Parsers - */ - -int term_parser_new(term_parser **out, bool host); -term_parser *term_parser_free(term_parser *parser); -int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw); - -#define _term_parser_free_ _cleanup_(term_parser_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_parser*, term_parser_free); - -/* - * Screens - */ - -enum { - TERM_KBDMOD_IDX_SHIFT, - TERM_KBDMOD_IDX_CTRL, - TERM_KBDMOD_IDX_ALT, - TERM_KBDMOD_IDX_LINUX, - TERM_KBDMOD_IDX_CAPS, - TERM_KBDMOD_CNT, - - TERM_KBDMOD_SHIFT = 1 << TERM_KBDMOD_IDX_SHIFT, - TERM_KBDMOD_CTRL = 1 << TERM_KBDMOD_IDX_CTRL, - TERM_KBDMOD_ALT = 1 << TERM_KBDMOD_IDX_ALT, - TERM_KBDMOD_LINUX = 1 << TERM_KBDMOD_IDX_LINUX, - TERM_KBDMOD_CAPS = 1 << TERM_KBDMOD_IDX_CAPS, -}; - -typedef int (*term_screen_write_fn) (term_screen *screen, void *userdata, const void *buf, size_t size); -typedef int (*term_screen_cmd_fn) (term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq); - -int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data); -term_screen *term_screen_ref(term_screen *screen); -term_screen *term_screen_unref(term_screen *screen); - -DEFINE_TRIVIAL_CLEANUP_FUNC(term_screen*, term_screen_unref); - -unsigned int term_screen_get_width(term_screen *screen); -unsigned int term_screen_get_height(term_screen *screen); -uint64_t term_screen_get_age(term_screen *screen); - -int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size); -int term_screen_feed_keyboard(term_screen *screen, - const uint32_t *keysyms, - size_t n_syms, - uint32_t ascii, - const uint32_t *ucs4, - unsigned int mods); -int term_screen_resize(term_screen *screen, unsigned int width, unsigned int height); -void term_screen_soft_reset(term_screen *screen); -void term_screen_hard_reset(term_screen *screen); - -int term_screen_set_answerback(term_screen *screen, const char *answerback); - -int term_screen_draw(term_screen *screen, - int (*draw_fn) (term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width), - void *userdata, - uint64_t *fb_age); diff --git a/src/libsystemd-terminal/test-term-page.c b/src/libsystemd-terminal/test-term-page.c deleted file mode 100644 index d59139b62d..0000000000 --- a/src/libsystemd-terminal/test-term-page.c +++ /dev/null @@ -1,459 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Terminal Page/Line/Cell/Char Tests - * This tests internals of terminal page, line, cell and char handling. It - * relies on some implementation details, so it might need to be updated if - * those internals are changed. They should be fairly obvious, though. - */ - -#include <stdio.h> -#include <string.h> -#include "macro.h" -#include "term-internal.h" - -#define MY_ASSERT_VALS __FILE__, __LINE__, __PRETTY_FUNCTION__ -#define MY_ASSERT_FORW _FILE, _LINE, _FUNC -#define MY_ASSERT_ARGS const char *_FILE, int _LINE, const char *_FUNC -#define MY_ASSERT(expr) \ - do { \ - if (_unlikely_(!(expr))) \ - log_assert_failed(#expr, _FILE, _LINE, _FUNC); \ - } while (false) \ - -/* - * Character Tests - * - * These tests rely on some implementation details of term_char_t, including - * the way we pack characters and the internal layout of "term_char_t". These - * tests have to be updated once we change the implementation. - */ - -#define PACK(v1, v2, v3) \ - TERM_CHAR_INIT( \ - (((((uint64_t)v1) & 0x1fffffULL) << 43) | \ - ((((uint64_t)v2) & 0x1fffffULL) << 22) | \ - ((((uint64_t)v3) & 0x1fffffULL) << 1) | \ - 0x1) \ - ) -#define PACK1(v1) PACK2((v1), 0x110000) -#define PACK2(v1, v2) PACK3((v1), (v2), 0x110000) -#define PACK3(v1, v2, v3) PACK((v1), (v2), (v3)) - -static void test_term_char_misc(void) { - term_char_t c, t; - - /* test TERM_CHAR_NULL handling */ - - c = TERM_CHAR_NULL; /* c is NULL */ - assert_se(term_char_same(c, TERM_CHAR_NULL)); - assert_se(term_char_equal(c, TERM_CHAR_NULL)); - assert_se(term_char_is_null(c)); - assert_se(term_char_is_null(TERM_CHAR_NULL)); - assert_se(!term_char_is_allocated(c)); - - /* test single char handling */ - - t = term_char_dup_append(c, 'A'); /* t is >A< now */ - assert_se(!term_char_same(c, t)); - assert_se(!term_char_equal(c, t)); - assert_se(!term_char_is_allocated(t)); - assert_se(!term_char_is_null(t)); - - /* test basic combined char handling */ - - t = term_char_dup_append(t, '~'); - t = term_char_dup_append(t, '^'); /* t is >A~^< now */ - assert_se(!term_char_same(c, t)); - assert_se(!term_char_is_allocated(t)); - assert_se(!term_char_is_null(t)); - - c = term_char_dup_append(c, 'A'); - c = term_char_dup_append(c, '~'); - c = term_char_dup_append(c, '^'); /* c is >A~^< now */ - assert_se(term_char_same(c, t)); - assert_se(term_char_equal(c, t)); - - /* test more than 2 comb-chars so the chars are allocated */ - - t = term_char_dup_append(t, '`'); /* t is >A~^`< now */ - c = term_char_dup_append(c, '`'); /* c is >A~^`< now */ - assert_se(!term_char_same(c, t)); - assert_se(term_char_equal(c, t)); - - /* test dup_append() on allocated chars */ - - term_char_free(t); - t = term_char_dup_append(c, '"'); /* t is >A~^`"< now */ - assert_se(!term_char_same(c, t)); - assert_se(!term_char_equal(c, t)); - c = term_char_merge(c, '"'); /* c is >A~^`"< now */ - assert_se(!term_char_same(c, t)); - assert_se(term_char_equal(c, t)); - - term_char_free(t); - term_char_free(c); -} - -static void test_term_char_packing(void) { - uint32_t seqs[][1024] = { - { -1 }, - { 0, -1 }, - { 'A', '~', -1 }, - { 'A', '~', 0, -1 }, - { 'A', '~', 'a', -1 }, - }; - term_char_t res[] = { - TERM_CHAR_NULL, - PACK1(0), - PACK2('A', '~'), - PACK3('A', '~', 0), - PACK3('A', '~', 'a'), - }; - uint32_t next; - unsigned int i, j; - term_char_t c = TERM_CHAR_NULL; - - /* - * This creates term_char_t objects based on the data in @seqs and - * compares the result to @res. Only basic packed types are tested, no - * allocations are done. - */ - - for (i = 0; i < ELEMENTSOF(seqs); ++i) { - for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) { - next = seqs[i][j]; - if (next == (uint32_t)-1) - break; - - c = term_char_merge(c, next); - } - - assert_se(!memcmp(&c, &res[i], sizeof(c))); - c = term_char_free(c); - } -} - -static void test_term_char_allocating(void) { - uint32_t seqs[][1024] = { - { 0, -1 }, - { 'A', '~', -1 }, - { 'A', '~', 0, -1 }, - { 'A', '~', 'a', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 }, - /* exceeding implementation-defined soft-limit of 64 */ - { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 }, - }; - term_char_t res[] = { - PACK1(0), - PACK2('A', '~'), - PACK3('A', '~', 0), - PACK3('A', '~', 'a'), - TERM_CHAR_NULL, /* allocated */ - TERM_CHAR_NULL, /* allocated */ - TERM_CHAR_NULL, /* allocated */ - }; - uint32_t str[][1024] = { - { 0, -1 }, - { 'A', '~', -1 }, - { 'A', '~', 0, -1 }, - { 'A', '~', 'a', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 }, - { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 }, - }; - size_t n; - uint32_t next; - unsigned int i, j; - const uint32_t *t; - - /* - * This builds term_char_t objects based on the data in @seqs. It - * compares the result to @res for packed chars, otherwise it requires - * them to be allocated. - * After that, we resolve the UCS-4 string and compare it to the - * expected strings in @str. - */ - - for (i = 0; i < ELEMENTSOF(seqs); ++i) { - _term_char_free_ term_char_t c = TERM_CHAR_NULL; - - for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) { - next = seqs[i][j]; - if (next == (uint32_t)-1) - break; - - c = term_char_merge(c, next); - } - - /* we use TERM_CHAR_NULL as marker for allocated chars here */ - if (term_char_is_null(res[i])) - assert_se(term_char_is_allocated(c)); - else - assert_se(!memcmp(&c, &res[i], sizeof(c))); - - t = term_char_resolve(c, &n, NULL); - for (j = 0; j < ELEMENTSOF(str[i]); ++j) { - next = str[i][j]; - if (next == (uint32_t)-1) - break; - - assert_se(t[j] == next); - } - - assert_se(n == j); - } -} - -/* - * Line Tests - * - * The following tests work on term_line objects and verify their behavior when - * we modify them. To verify and set line layouts, we have two simple helpers - * to avoid harcoding the cell-verification all the time: - * line_set(): Set a line to a given layout - * line_assert(): Verify that a line has a given layout - * - * These functions take the line-layout encoded as a string and verify it - * against, or set it on, a term_line object. The format used to describe a - * line looks like this: - * example: "| | A | | | | | | 10 *AB* |" - * - * The string describes the contents of all cells of a line, separated by - * pipe-symbols ('|'). Whitespace are ignored, the leading pipe-symbol is - * optional. - * The description of each cell can contain an arbitrary amount of characters - * in the range 'A'-'Z', 'a'-'z'. All those are combined and used as term_char_t - * on this cell. Any numbers in the description are combined and are used as - * cell-age. - * The occurrence of a '*'-symbol marks the cell as bold, '/' marks it as italic. - * You can use those characters multiple times, but only the first one has an - * effect. - * For further symbols, see parse_attr(). - * - * Therefore, the following descriptions are equivalent: - * 1) "| | /A* | | | | | | 10 *AB* |" - * 2) "| | /A** | | | | | | 10 *AB* |" - * 3) "| | A* // | | | | | | 10 *AB* |" - * 4) "| | A* // | | | | | | 1 *AB* 0 |" - * 5) "| | A* // | | | | | | A1B0* |" - * - * The parser isn't very strict about placement of alpha/numerical characters, - * but simply appends all found chars. Don't make use of that feature! It's - * just a stupid parser to simplify these tests. Make them readable! - */ - -static void parse_attr(char c, term_char_t *ch, term_attr *attr, term_age_t *age) { - switch (c) { - case ' ': - /* ignore */ - break; - case '0' ... '9': - /* increase age */ - *age = *age * 10; - *age = *age + c - '0'; - break; - case 'A' ... 'Z': - case 'a' ... 'z': - /* add to character */ - *ch = term_char_merge(*ch, c); - break; - case '*': - attr->bold = true; - break; - case '/': - attr->italic = true; - break; - default: - assert_se(0); - break; - } -} - -static void cell_assert(MY_ASSERT_ARGS, term_cell *c, term_char_t ch, const term_attr *attr, term_age_t age) { - MY_ASSERT(term_char_equal(c->ch, ch)); - MY_ASSERT(!memcmp(&c->attr, attr, sizeof(*attr))); - MY_ASSERT(c->age == age); -} -#define CELL_ASSERT(_cell, _ch, _attr, _age) cell_assert(MY_ASSERT_VALS, (_cell), (_ch), (_attr), (_age)) - -static void line_assert(MY_ASSERT_ARGS, term_line *l, const char *str, unsigned int fill) { - unsigned int cell_i; - term_char_t ch = TERM_CHAR_NULL; - term_attr attr = { }; - term_age_t age = TERM_AGE_NULL; - char c; - - assert_se(l->fill == fill); - - /* skip leading whitespace */ - while (*str == ' ') - ++str; - - /* skip leading '|' */ - if (*str == '|') - ++str; - - cell_i = 0; - while ((c = *str++)) { - switch (c) { - case '|': - /* end of cell-description; compare it */ - assert_se(cell_i < l->n_cells); - cell_assert(MY_ASSERT_FORW, - &l->cells[cell_i], - ch, - &attr, - age); - - ++cell_i; - ch = term_char_free(ch); - zero(attr); - age = TERM_AGE_NULL; - break; - default: - parse_attr(c, &ch, &attr, &age); - break; - } - } - - assert_se(cell_i == l->n_cells); -} -#define LINE_ASSERT(_line, _str, _fill) line_assert(MY_ASSERT_VALS, (_line), (_str), (_fill)) - -static void line_set(term_line *l, unsigned int pos, const char *str, bool insert_mode) { - term_char_t ch = TERM_CHAR_NULL; - term_attr attr = { }; - term_age_t age = TERM_AGE_NULL; - char c; - - while ((c = *str++)) - parse_attr(c, &ch, &attr, &age); - - term_line_write(l, pos, ch, 1, &attr, age, insert_mode); -} - -static void line_resize(term_line *l, unsigned int width, const term_attr *attr, term_age_t age) { - assert_se(term_line_reserve(l, width, attr, age, width) >= 0); - term_line_set_width(l, width); -} - -static void test_term_line_misc(void) { - term_line *l; - - assert_se(term_line_new(&l) >= 0); - assert_se(!term_line_free(l)); - - assert_se(term_line_new(NULL) < 0); - assert_se(!term_line_free(NULL)); - - assert_se(term_line_new(&l) >= 0); - assert_se(l->n_cells == 0); - assert_se(l->fill == 0); - assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0); - assert_se(l->n_cells == 16); - assert_se(l->fill == 0); - assert_se(term_line_reserve(l, 512, NULL, 0, 0) >= 0); - assert_se(l->n_cells == 512); - assert_se(l->fill == 0); - assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0); - assert_se(l->n_cells == 512); - assert_se(l->fill == 0); - assert_se(!term_line_free(l)); -} - -static void test_term_line_ops(void) { - term_line *l; - term_attr attr_regular = { }; - term_attr attr_bold = { .bold = true }; - term_attr attr_italic = { .italic = true }; - - assert_se(term_line_new(&l) >= 0); - line_resize(l, 8, NULL, 0); - assert_se(l->n_cells == 8); - - LINE_ASSERT(l, "| | | | | | | | |", 0); - - term_line_write(l, 4, TERM_CHAR_NULL, 0, NULL, TERM_AGE_NULL, 0); - LINE_ASSERT(l, "| | | | | | | | |", 5); - - term_line_write(l, 1, PACK1('A'), 1, NULL, TERM_AGE_NULL, 0); - LINE_ASSERT(l, "| |A| | | | | | |", 5); - - term_line_write(l, 8, PACK2('A', 'B'), 1, NULL, TERM_AGE_NULL, 0); - LINE_ASSERT(l, "| |A| | | | | | |", 5); - - term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_regular, 10, 0); - LINE_ASSERT(l, "| |A| | | | | | 10 AB |", 8); - - term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_bold, 10, 0); - LINE_ASSERT(l, "| |A| | | | | | 10 *AB* |", 8); - - term_line_reset(l, NULL, TERM_AGE_NULL); - - LINE_ASSERT(l, "| | | | | | | | |", 0); - line_set(l, 2, "*wxyz* 8", 0); - line_set(l, 3, "/wxyz/ 8", 0); - LINE_ASSERT(l, "| | | *wxyz* 8 | /wxyz/ 8 | | | | |", 4); - line_set(l, 2, "*abc* 9", true); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | 9 |", 5); - line_set(l, 7, "*abc* 10", true); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | *abc* 10 |", 8); - - term_line_erase(l, 6, 1, NULL, 11, 0); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 11 | *abc* 10 |", 8); - term_line_erase(l, 6, 2, &attr_italic, 12, 0); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 12 // |", 6); - term_line_erase(l, 7, 2, &attr_regular, 13, 0); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 13 |", 6); - term_line_delete(l, 1, 3, &attr_bold, 14); - LINE_ASSERT(l, "| | /wxyz/ 14 | 14 | 14 // | 14 | 14 ** | 14 ** | 14 ** |", 3); - term_line_insert(l, 2, 2, &attr_regular, 15); - LINE_ASSERT(l, "| | /wxyz/ 14 | 15 | 15 | 15 | 15 // | 15 | 15 ** |", 5); - - assert_se(!term_line_free(l)); -} - -int main(int argc, char *argv[]) { - test_term_char_misc(); - test_term_char_packing(); - test_term_char_allocating(); - - test_term_line_misc(); - test_term_line_ops(); - - return 0; -} diff --git a/src/libsystemd-terminal/test-term-parser.c b/src/libsystemd-terminal/test-term-parser.c deleted file mode 100644 index e40b267b1c..0000000000 --- a/src/libsystemd-terminal/test-term-parser.c +++ /dev/null @@ -1,141 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Terminal Parser Tests - */ - -#include <stdio.h> -#include <string.h> -#include "macro.h" -#include "term-internal.h" -#include "utf8.h" - -static void test_term_utf8_invalid(void) { - term_utf8 p = { }; - uint32_t *res; - size_t len; - - len = term_utf8_decode(NULL, NULL, 0); - assert_se(!len); - - len = term_utf8_decode(&p, NULL, 0); - assert_se(len == 1); - - res = NULL; - len = term_utf8_decode(NULL, &res, 0); - assert_se(!len); - assert_se(res != NULL); - assert_se(!*res); - - len = term_utf8_decode(&p, &res, 0); - assert_se(len == 1); - assert_se(res != NULL); - assert_se(!*res); - - len = term_utf8_decode(&p, &res, 0xCf); - assert_se(len == 0); - assert_se(res != NULL); - assert_se(!*res); - - len = term_utf8_decode(&p, &res, 0); - assert_se(len == 2); - assert_se(res != NULL); - assert_se(res[0] == 0xCf && res[1] == 0); -} - -static void test_term_utf8_range(void) { - term_utf8 p = { }; - uint32_t *res; - char u8[4]; - uint32_t i, j; - size_t ulen, len; - - /* Convert all ucs-4 chars to utf-8 and back */ - - for (i = 0; i < 0x10FFFF; ++i) { - ulen = utf8_encode_unichar(u8, i); - if (!ulen) - continue; - - for (j = 0; j < ulen; ++j) { - len = term_utf8_decode(&p, &res, u8[j]); - if (len < 1) { - assert_se(j + 1 != ulen); - continue; - } - - assert_se(j + 1 == ulen); - assert_se(len == 1 && *res == i); - assert_se(i <= 127 || ulen >= 2); - } - } -} - -static void test_term_utf8_mix(void) { - static const char source[] = { - 0x00, /* normal 0 */ - 0xC0, 0x80, /* overlong 0 */ - 0xC0, 0x81, /* overlong 1 */ - 0xE0, 0x80, 0x81, /* overlong 1 */ - 0xF0, 0x80, 0x80, 0x81, /* overlong 1 */ - 0xC0, 0x00, /* invalid continuation */ - 0xC0, 0xC0, 0x81, /* invalid continuation with a following overlong 1 */ - 0xF8, 0x80, 0x80, 0x80, 0x81, /* overlong 1 with 5 bytes */ - 0xE0, 0x80, 0xC0, 0x81, /* invalid 3-byte followed by valid 2-byte */ - 0xF0, 0x80, 0x80, 0xC0, 0x81, /* invalid 4-byte followed by valid 2-byte */ - }; - static const uint32_t result[] = { - 0x0000, - 0x0000, - 0x0001, - 0x0001, - 0x0001, - 0x00C0, 0x0000, - 0x00C0, 0x0001, - 0x00F8, 0x0080, 0x0080, 0x0080, 0x0081, - 0x00E0, 0x0080, 0x0001, - 0x00F0, 0x0080, 0x0080, 0x0001, - }; - term_utf8 p = { }; - uint32_t *res; - unsigned int i, j; - size_t len; - - for (i = 0, j = 0; i < sizeof(source); ++i) { - len = term_utf8_decode(&p, &res, source[i]); - if (len < 1) - continue; - - assert_se(j + len <= ELEMENTSOF(result)); - assert_se(!memcmp(res, &result[j], sizeof(uint32_t) * len)); - j += len; - } - - assert_se(j == ELEMENTSOF(result)); -} - -int main(int argc, char *argv[]) { - test_term_utf8_invalid(); - test_term_utf8_range(); - test_term_utf8_mix(); - - return 0; -} diff --git a/src/libsystemd-terminal/test-unifont.c b/src/libsystemd-terminal/test-unifont.c deleted file mode 100644 index 2366d38574..0000000000 --- a/src/libsystemd-terminal/test-unifont.c +++ /dev/null @@ -1,125 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Test Unifont Helper - * This tries opening the binary unifont glyph-array and renders some glyphs. - * The glyphs are then compared to hard-coded glyphs. - */ - -#include <stdio.h> -#include <string.h> -#include "macro.h" -#include "unifont-def.h" -#include "unifont.h" - -static void render(char *w, const unifont_glyph *g) { - unsigned int i, j; - const uint8_t *d = g->data; - - for (j = 0; j < 16; ++j) { - for (i = 0; i < 8 * g->cwidth; ++i) { - if (d[i / 8] & (1 << (7 - i % 8))) - *w++ = '#'; - else - *w++ = ' '; - } - *w++ = '\n'; - d += g->stride; - } - - *w++ = 0; -} - -static void test_unifont(void) { - char buf[4096]; - unifont_glyph g; - unifont *u; - - assert_se(unifont_new(&u) >= 0); - - /* lookup invalid font */ - assert_se(unifont_lookup(u, &g, 0xffffffffU) < 0); - - /* lookup and render 'A' */ - assert_se(unifont_lookup(u, &g, 'A') >= 0); - assert_se(g.width == 8); - assert_se(g.height == 16); - assert_se(g.stride >= 1); - assert_se(g.cwidth == 1); - assert_se(g.data != NULL); - render(buf, &g); - assert_se(!strcmp(buf, - " \n" - " \n" - " \n" - " \n" - " ## \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " ###### \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " \n" - " \n" - )); - - /* lookup and render '什' */ - assert_se(unifont_lookup(u, &g, 0x4ec0) >= 0); - assert_se(g.width == 16); - assert_se(g.height == 16); - assert_se(g.stride >= 2); - assert_se(g.cwidth == 2); - assert_se(g.data != NULL); - render(buf, &g); - assert_se(!strcmp(buf, - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " ## # \n" - " ## ########## \n" - " # # # \n" - "# # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - )); - - unifont_unref(u); -} - -int main(int argc, char **argv) { - if (access(UNIFONT_PATH, F_OK)) - return 77; - - test_unifont(); - - return 0; -} diff --git a/src/libsystemd-terminal/unifont-def.h b/src/libsystemd-terminal/unifont-def.h deleted file mode 100644 index 3847a2cf6c..0000000000 --- a/src/libsystemd-terminal/unifont-def.h +++ /dev/null @@ -1,137 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include "sparse-endian.h" -#include "util.h" - -typedef struct unifont_header unifont_header; -typedef struct unifont_glyph_header unifont_glyph_header; - -/* - * Unifont: On-disk data - * Conventional font-formats have the problem that you have to pre-render each - * glyph before you can use it. If you just need one glyph, you have to parse - * the font-file until you found that glyph. - * GNU-Unifont is a bitmap font with very good Unicode coverage. All glyphs are - * (n*8)x16 bitmaps. Our on-disk data stores all those glyphs pre-compiled with - * fixed offsets. Therefore, the font-file can be mmap()ed and all glyphs can - * be accessed in O(1) (because all glyphs have the same size and thus their - * offsets can be easily computed). This guarantees, that the kernel only loads - * the pages that are really accessed. Thus, we have a far lower overhead than - * traditional font-formats like BDF. Furthermore, the backing file is read-only - * and can be shared in memory between multiple users. - * - * The binary-format starts with a fixed header: - * - * | 2bytes | 2bytes | 2bytes | 2bytes | - * - * +-----------------------------------+ - * | SIGNATURE | 8 bytes - * +-----------------+-----------------+ - * | COMPAT FLAGS | INCOMPAT FLAGS | 8 bytes - * +-----------------+--------+--------+ - * | HEADER SIZE |GH-SIZE |G-STRIDE| 8 bytes - * +-----------------+--------+--------+ - * | GLYPH BODY SIZE | 8 bytes - * +-----------------------------------+ - * - * * The 8 bytes signature must be set to the ASCII string "DVDHRMUF". - * * The 4 bytes compatible-flags field contains flags for new features that - * might be added in the future and which are compatible to older parsers. - * * The 4 bytes incompatible-flags field contains flags for new features that - * might be added in the future and which are incompatible to old parses. - * Thus, if you encounter an unknown bit set, you must abort! - * * The 4 bytes header-size field contains the size of the header in bytes. It - * must be at least 32 (the size of this fixed header). If new features are - * added, it might be increased. It can also be used to add padding to the - * end of the header. - * * The 2 bytes glyph-header-size field specifies the size of each glyph - * header in bytes (see below). - * * The 2 bytes glyph-stride field specifies the stride of each line of glyph - * data in "bytes per line". - * * The 8 byte glyph-body-size field defines the size of each glyph body in - * bytes. - * - * After the header, the file can contain padding bytes, depending on the - * header-size field. Everything beyond the header+padding is treated as a big - * array of glyphs. Each glyph looks like this: - * - * | 1 byte | - * - * +-----------------------------------+ - * | WIDTH | 1 byte - * +-----------------------------------+ - * ~ PADDING ~ - * +-----------------------------------+ - * ~ ~ - * ~ ~ - * ~ DATA ~ - * ~ ~ - * ~ ~ - * +-----------------------------------+ - * - * * The first byte specifies the width of the glyph. If it is 0, the glyph - * must be treated as non-existent. - * All glyphs are "8*n" pixels wide and "16" pixels high. The width-field - * specifies the width multiplier "n". - * * After the width field padding might be added. This depends on the global - * glyph-header-size field. It defines the total size of each glyph-header. - * After the glyph-header+padding, the data-field starts. - * * The data-field contains a byte-array of bitmap data. The array is always - * as big as specified in the global glyph-body-size header field. This might - * include padding. - * The array contains all 16 lines of bitmap information for that glyph. The - * stride is given in the global glyph-stride header field. This can be used - * to add padding after each line. - * Each line is encoded as 1 bit per pixel bitmap. That is, each byte encodes - * data for 8 pixels (left most pixel is encoded in the LSB, right most pixel - * in the MSB). The width field defines the number of bytes valid per line. - * For width==1, you need 1 byte to encode the 8 pixels. The stride defines - * where the encoding of the next line starts. - * Any data beyond the 16th line is padding and must be ignored. - */ - -/* path to binary file */ -#define UNIFONT_PATH "/usr/share/systemd/unifont-glyph-array.bin" - -/* header-size of version 1 */ -#define UNIFONT_HEADER_SIZE_MIN 32 - -struct unifont_header { - /* fields available in version 1 */ - uint8_t signature[8]; - le32_t compatible_flags; - le32_t incompatible_flags; - le32_t header_size; - le16_t glyph_header_size; - le16_t glyph_stride; - le64_t glyph_body_size; -} _packed_; - -struct unifont_glyph_header { - /* fields available in version 1 */ - uint8_t width; -} _packed_; diff --git a/src/libsystemd-terminal/unifont.c b/src/libsystemd-terminal/unifont.c deleted file mode 100644 index 0da81e8ff2..0000000000 --- a/src/libsystemd-terminal/unifont.c +++ /dev/null @@ -1,238 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -/* - * Unifont - * This implements the unifont glyph-array parser and provides it via a simple - * API to the caller. No heavy transformations are performed so glyph-lookups - * stay as fast as possible. - */ - -#include <endian.h> -#include <fcntl.h> -#include <stdint.h> -#include <stdlib.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include "macro.h" -#include "unifont-def.h" -#include "unifont.h" -#include "util.h" - -struct unifont { - unsigned long ref; - - int fd; - const uint8_t *map; - size_t size; - - unifont_header header; - const void *glyphs; /* unaligned! */ - size_t n_glyphs; - size_t glyphsize; -}; - -static int unifont_fetch_header(unifont *u) { - unifont_header h = { }; - uint64_t glyphsize; - - if (u->size < UNIFONT_HEADER_SIZE_MIN) - return -EBFONT; - - assert_cc(sizeof(h) >= UNIFONT_HEADER_SIZE_MIN); - memcpy(&h, u->map, UNIFONT_HEADER_SIZE_MIN); - - h.compatible_flags = le32toh(h.compatible_flags); - h.incompatible_flags = le32toh(h.incompatible_flags); - h.header_size = le32toh(h.header_size); - h.glyph_header_size = le16toh(h.glyph_header_size); - h.glyph_stride = le16toh(h.glyph_stride); - h.glyph_body_size = le64toh(h.glyph_body_size); - - if (memcmp(h.signature, "DVDHRMUF", 8)) - return -EBFONT; - if (h.incompatible_flags != 0) - return -EBFONT; - if (h.header_size < UNIFONT_HEADER_SIZE_MIN || h.header_size > u->size) - return -EBFONT; - if (h.glyph_header_size + h.glyph_body_size < h.glyph_header_size) - return -EBFONT; - if (h.glyph_stride * 16ULL > h.glyph_body_size) - return -EBFONT; - - glyphsize = h.glyph_header_size + h.glyph_body_size; - - if (glyphsize == 0 || glyphsize > u->size - h.header_size) { - u->n_glyphs = 0; - } else { - u->glyphs = u->map + h.header_size; - u->n_glyphs = (u->size - h.header_size) / glyphsize; - u->glyphsize = glyphsize; - } - - memcpy(&u->header, &h, sizeof(h)); - return 0; -} - -static int unifont_fetch_glyph(unifont *u, unifont_glyph_header *out_header, const void **out_body, uint32_t ucs4) { - unifont_glyph_header glyph_header = { }; - const void *glyph_body = NULL; - const uint8_t *p; - - if (ucs4 >= u->n_glyphs) - return -ENOENT; - - p = u->glyphs; - - /* copy glyph-header data */ - p += ucs4 * u->glyphsize; - memcpy(&glyph_header, p, MIN(sizeof(glyph_header), u->header.glyph_header_size)); - - /* copy glyph-body pointer */ - p += u->header.glyph_header_size; - glyph_body = p; - - if (glyph_header.width < 1) - return -ENOENT; - if (glyph_header.width > u->header.glyph_stride) - return -EBFONT; - - memcpy(out_header, &glyph_header, sizeof(glyph_header)); - *out_body = glyph_body; - return 0; -} - -int unifont_new(unifont **out) { - _cleanup_(unifont_unrefp) unifont *u = NULL; - struct stat st; - int r; - - assert_return(out, -EINVAL); - - u = new0(unifont, 1); - if (!u) - return -ENOMEM; - - u->ref = 1; - u->fd = -1; - u->map = MAP_FAILED; - - u->fd = open(UNIFONT_PATH, O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (u->fd < 0) - return -errno; - - r = fstat(u->fd, &st); - if (r < 0) - return -errno; - - u->size = st.st_size; - u->map = mmap(NULL, u->size, PROT_READ, MAP_PRIVATE, u->fd, 0); - if (u->map == MAP_FAILED) - return -errno; - - r = unifont_fetch_header(u); - if (r < 0) - return r; - - *out = u; - u = NULL; - return 0; -} - -unifont *unifont_ref(unifont *u) { - if (!u || !u->ref) - return NULL; - - ++u->ref; - - return u; -} - -unifont *unifont_unref(unifont *u) { - if (!u || !u->ref || --u->ref) - return NULL; - - if (u->map != MAP_FAILED) - munmap((void*)u->map, u->size); - u->fd = safe_close(u->fd); - free(u); - - return NULL; -} - -unsigned int unifont_get_width(unifont *u) { - assert(u); - - return 8U; -} - -unsigned int unifont_get_height(unifont *u) { - assert(u); - - return 16U; -} - -unsigned int unifont_get_stride(unifont *u) { - assert(u); - - return u->header.glyph_stride; -} - -int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4) { - unifont_glyph_header h = { }; - const void *b = NULL; - unifont_glyph g = { }; - int r; - - assert_return(u, -EINVAL); - - r = unifont_fetch_glyph(u, &h, &b, ucs4); - if (r < 0) - return r; - - g.width = h.width * 8U; - g.height = 16U; - g.stride = u->header.glyph_stride; - g.cwidth = h.width; - g.data = b; - - if (out) - memcpy(out, &g, sizeof(g)); - return 0; -} - -void unifont_fallback(unifont_glyph *out) { - static const uint8_t fallback_data[] = { - /* unifont 0xfffd '�' (unicode replacement character) */ - 0x00, 0x00, 0x00, 0x7e, - 0x66, 0x5a, 0x5a, 0x7a, - 0x76, 0x76, 0x7e, 0x76, - 0x76, 0x7e, 0x00, 0x00, - }; - - assert(out); - - out->width = 8; - out->height = 16; - out->stride = 1; - out->cwidth = 1; - out->data = fallback_data; -} diff --git a/src/libsystemd-terminal/unifont.h b/src/libsystemd-terminal/unifont.h deleted file mode 100644 index 74ee5ecb3c..0000000000 --- a/src/libsystemd-terminal/unifont.h +++ /dev/null @@ -1,54 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#pragma once - -#include <stdint.h> - -typedef struct unifont unifont; -typedef struct unifont_glyph unifont_glyph; - -/* - * Unifont - * The unifont API provides a glyph-lookup for bitmap fonts which can be used - * as fallback if no system-font is available or if you don't want to deal with - * full font renderers. - */ - -struct unifont_glyph { - unsigned int width; - unsigned int height; - unsigned int stride; - unsigned int cwidth; - const void *data; /* unaligned! */ -}; - -int unifont_new(unifont **out); -unifont *unifont_ref(unifont *u); -unifont *unifont_unref(unifont *u); - -DEFINE_TRIVIAL_CLEANUP_FUNC(unifont*, unifont_unref); - -unsigned int unifont_get_width(unifont *u); -unsigned int unifont_get_height(unifont *u); -unsigned int unifont_get_stride(unifont *u); -int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4); -void unifont_fallback(unifont_glyph *out); diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 0dbfbddcf6..f2092795f4 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -46,6 +46,8 @@ #define BUS_ERROR_NO_MACHINE_FOR_PID "org.freedesktop.machine1.NoMachineForPID" #define BUS_ERROR_MACHINE_EXISTS "org.freedesktop.machine1.MachineExists" #define BUS_ERROR_NO_PRIVATE_NETWORKING "org.freedesktop.machine1.NoPrivateNetworking" +#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping" +#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping" #define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession" #define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID" diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c index a38c5c50fc..c53666ddd0 100644 --- a/src/libsystemd/sd-bus/bus-control.c +++ b/src/libsystemd/sd-bus/bus-control.c @@ -1131,7 +1131,7 @@ static int add_name_change_match(sd_bus *bus, /* If the old name is unset or empty, then * this can match against added names */ - if (!old_owner || old_owner[0] == 0) { + if (isempty(old_owner)) { item->type = KDBUS_ITEM_NAME_ADD; r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); @@ -1141,7 +1141,7 @@ static int add_name_change_match(sd_bus *bus, /* If the new name is unset or empty, then * this can match against removed names */ - if (!new_owner || new_owner[0] == 0) { + if (isempty(new_owner)) { item->type = KDBUS_ITEM_NAME_REMOVE; r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); @@ -1185,8 +1185,10 @@ static int add_name_change_match(sd_bus *bus, /* If the old name is unset or empty, then this can * match against added ids */ - if (!old_owner || old_owner[0] == 0) { + if (isempty(old_owner)) { item->type = KDBUS_ITEM_ID_ADD; + if (!isempty(new_owner)) + item->id_change.id = new_owner_id; r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); if (r < 0) @@ -1195,8 +1197,10 @@ static int add_name_change_match(sd_bus *bus, /* If thew new name is unset or empty, then this can * match against removed ids */ - if (!new_owner || new_owner[0] == 0) { + if (isempty(new_owner)) { item->type = KDBUS_ITEM_ID_REMOVE; + if (!isempty(old_owner)) + item->id_change.id = old_owner_id; r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); if (r < 0) @@ -1345,6 +1349,10 @@ int bus_add_match_internal_kernel( else if (r > 0) sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); + /* if not a broadcast, it cannot be a name-change */ + if (r <= 0 || dst_id != KDBUS_DST_ID_BROADCAST) + matches_name_change = false; + break; } diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c index e3fac01f92..6ac5ebc3da 100644 --- a/src/libsystemd/sd-bus/bus-kernel.c +++ b/src/libsystemd/sd-bus/bus-kernel.c @@ -1332,8 +1332,7 @@ static int bus_kernel_translate_message(sd_bus *bus, struct kdbus_msg *k) { KDBUS_ITEM_FOREACH(d, k, items) { if (d->type == KDBUS_ITEM_TIMESTAMP) ts = &d->timestamp; - - if (d->type >= _KDBUS_ITEM_KERNEL_BASE && d->type < _KDBUS_ITEM_KERNEL_BASE + ELEMENTSOF(translate)) { + else if (d->type >= _KDBUS_ITEM_KERNEL_BASE && d->type < _KDBUS_ITEM_KERNEL_BASE + ELEMENTSOF(translate)) { if (found) return -EBADMSG; found = d; diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index 983e2f62cd..18685be8ff 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -2161,6 +2161,7 @@ static int bus_message_close_variant(sd_bus_message *m, struct bus_container *c) } static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, bool add_offset) { + bool fixed_size = true; size_t n_variable = 0; unsigned i = 0; const char *p; @@ -2196,6 +2197,8 @@ static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, /* We need to add an offset for each item that has a * variable size and that is not the last one in the * list */ + if (r == 0) + fixed_size = false; if (r == 0 && p[n] != 0) n_variable++; @@ -2207,7 +2210,19 @@ static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, assert(c->need_offsets || n_variable == 0); if (n_variable <= 0) { - a = message_extend_body(m, 1, 0, add_offset, false); + int alignment = 1; + + /* Structures with fixed-size members only have to be + * fixed-size themselves. But gvariant requires all fixed-size + * elements to be sized a multiple of their alignment. Hence, + * we must *always* add final padding after the last member so + * the overall size of the structure is properly aligned. */ + if (fixed_size) + alignment = bus_gvariant_get_alignment(strempty(c->signature)); + + assert(alignment > 0); + + a = message_extend_body(m, alignment, 0, add_offset, false); if (!a) return -ENOMEM; } else { diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index 2eaa7de306..c25293e5e9 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -68,6 +68,12 @@ static int node_vtable_get_userdata( return 1; } +static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); + + return (uint8_t*) u + p->x.method.offset; +} + static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { assert(p); @@ -167,6 +173,7 @@ static int add_subtree_to_set( sd_bus *bus, const char *prefix, struct node *n, + bool skip_subhierarchies, Set *s, sd_bus_error *error) { @@ -198,11 +205,13 @@ static int add_subtree_to_set( if (r < 0 && r != -EEXIST) return r; - r = add_subtree_to_set(bus, prefix, i, s, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; + if (!skip_subhierarchies || !i->object_managers) { + r = add_subtree_to_set(bus, prefix, i, skip_subhierarchies, s, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } } return 0; @@ -212,6 +221,7 @@ static int get_child_nodes( sd_bus *bus, const char *prefix, struct node *n, + bool skip_subhierarchies, Set **_s, sd_bus_error *error) { @@ -227,7 +237,7 @@ static int get_child_nodes( if (!s) return -ENOMEM; - r = add_subtree_to_set(bus, prefix, n, s, error); + r = add_subtree_to_set(bus, prefix, n, skip_subhierarchies, s, error); if (r < 0) { set_free_free(s); return r; @@ -360,6 +370,8 @@ static int method_callbacks_run( if (bus->nodes_modified) return 0; + u = vtable_method_convert_userdata(c->vtable, u); + *found_object = true; if (c->last_iteration == bus->iteration_counter) @@ -892,7 +904,7 @@ static int process_introspect( assert(n); assert(found_object); - r = get_child_nodes(bus, m->path, n, &s, &error); + r = get_child_nodes(bus, m->path, n, false, &s, &error); if (r < 0) return bus_maybe_reply_error(m, r, &error); if (bus->nodes_modified) @@ -1158,12 +1170,16 @@ static int process_get_managed_objects( if (require_fallback || !n->object_managers) return 0; - r = get_child_nodes(bus, m->path, n, &s, &error); + r = get_child_nodes(bus, m->path, n, true, &s, &error); if (r < 0) return r; if (bus->nodes_modified) return 0; + r = set_put_strdup(s, m->path); + if (r < 0) + return r; + r = sd_bus_message_new_method_return(m, &reply); if (r < 0) return r; @@ -1412,7 +1428,7 @@ static struct node *bus_node_allocate(sd_bus *bus, const char *path) { e = strrchr(path, '/'); assert(e); - p = strndupa(path, MAX(1, path - e)); + p = strndupa(path, MAX(1, e - path)); parent = bus_node_allocate(bus, p); if (!parent) @@ -1463,6 +1479,32 @@ void bus_node_gc(sd_bus *b, struct node *n) { free(n); } +static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path) { + struct node *n; + + assert(bus); + assert(path); + + n = hashmap_get(bus->nodes, path); + if (!n) { + char *prefix; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + n = hashmap_get(bus->nodes, prefix); + if (n) + break; + } + } + + while (n && !n->object_managers) + n = n->parent; + + if (out) + *out = n; + return !!n; +} + static int bus_add_object( sd_bus *bus, sd_bus_slot **slot, @@ -2265,6 +2307,7 @@ _public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) { BUS_DONT_DESTROY(bus); _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + struct node *object_manager; int r; /* @@ -2285,11 +2328,17 @@ _public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) { if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + do { bus->nodes_modified = false; m = sd_bus_message_unref(m); - r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); if (r < 0) return r; @@ -2428,6 +2477,7 @@ _public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) { BUS_DONT_DESTROY(bus); _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + struct node *object_manager; int r; /* @@ -2448,11 +2498,17 @@ _public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) { if (!BUS_IS_OPEN(bus->state)) return -ENOTCONN; + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + do { bus->nodes_modified = false; m = sd_bus_message_unref(m); - r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); if (r < 0) return r; @@ -2584,6 +2640,7 @@ _public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, ch BUS_DONT_DESTROY(bus); _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + struct node *object_manager; char **i; int r; @@ -2597,11 +2654,17 @@ _public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, ch if (strv_isempty(interfaces)) return 0; + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + do { bus->nodes_modified = false; m = sd_bus_message_unref(m); - r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); if (r < 0) return r; @@ -2661,6 +2724,7 @@ _public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const c _public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) { _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + struct node *object_manager; int r; assert_return(bus, -EINVAL); @@ -2673,7 +2737,13 @@ _public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, if (strv_isempty(interfaces)) return 0; - r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); if (r < 0) return r; diff --git a/src/libsystemd/sd-bus/bus-slot.c b/src/libsystemd/sd-bus/bus-slot.c index c452477566..b149ea16da 100644 --- a/src/libsystemd/sd-bus/bus-slot.c +++ b/src/libsystemd/sd-bus/bus-slot.c @@ -273,7 +273,7 @@ _public_ int sd_bus_slot_set_description(sd_bus_slot *slot, const char *descript return free_and_strdup(&slot->description, description); } -_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, char **description) { +_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description) { assert_return(slot, -EINVAL); assert_return(description, -EINVAL); assert_return(slot->description, -ENXIO); diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 322d57ddbb..735a775cb4 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -264,6 +264,8 @@ static bool line_begins(const char *s, size_t m, const char *word) { static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { _cleanup_free_ char *token = NULL; + size_t len; + int r; if (!b->anonymous_auth) return 0; @@ -276,11 +278,12 @@ static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { if (l % 2 != 0) return 0; - token = unhexmem(p, l); - if (!token) - return -ENOMEM; - if (memchr(token, 0, l/2)) + r = unhexmem(p, l, (void **) &token, &len); + if (r < 0) + return 0; + + if (memchr(token, 0, len)) return 0; return !!utf8_is_valid(token); @@ -288,6 +291,7 @@ static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { static int verify_external_token(sd_bus *b, const char *p, size_t l) { _cleanup_free_ char *token = NULL; + size_t len; uid_t u; int r; @@ -307,11 +311,11 @@ static int verify_external_token(sd_bus *b, const char *p, size_t l) { if (l % 2 != 0) return 0; - token = unhexmem(p, l); - if (!token) - return -ENOMEM; + r = unhexmem(p, l, (void**) &token, &len); + if (r < 0) + return 0; - if (memchr(token, 0, l/2)) + if (memchr(token, 0, len)) return 0; r = parse_uid(token, &u); diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index a866a56179..59deaea89f 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -131,6 +131,9 @@ int main(int argc, char *argv[]) { r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo"); assert_se(r >= 0); + r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10); + assert_se(r >= 0); + r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3"); assert_se(r >= 0); @@ -252,6 +255,22 @@ int main(int argc, char *argv[]) { assert_se(v == 5); assert_se(streq(y, "waldo")); + r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u); + assert_se(r > 0); + assert_se(v == 8); + assert_se(u64 == 777); + assert_se(u == 7); + + r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64); + assert_se(r > 0); + assert_se(v == 9); + assert_se(u == 77); + assert_se(u64 == 7777); + + r = sd_bus_message_read(m, "y", &v); + assert_se(r > 0); + assert_se(v == 10); + r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d); assert_se(r > 0); assert_se(boolean); @@ -331,7 +350,7 @@ int main(int argc, char *argv[]) { assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0); - r = sd_bus_message_skip(m, "a{yv}"); + r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y"); assert_se(r >= 0); assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0); diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c index 52952603e4..359984c7f3 100644 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ b/src/libsystemd/sd-bus/test-bus-objects.c @@ -115,14 +115,13 @@ static int set_handler(sd_bus *bus, const char *path, const char *interface, con static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { _cleanup_free_ char *s = NULL; - const char *x; int r; assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0); r = sd_bus_message_append(reply, "s", s); assert_se(r >= 0); - assert_se(x = startswith(path, "/value/")); + assert_se(startswith(path, "/value/") != NULL || strcmp(path, "/value") == 0); assert_se(PTR_TO_UINT(userdata) == 30); @@ -154,7 +153,7 @@ static int notify_test2(sd_bus_message *m, void *userdata, sd_bus_error *error) static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { int r; - assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.test", NULL) >= 0); + assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); r = sd_bus_reply_method_return(m, NULL); assert_se(r >= 0); @@ -165,7 +164,7 @@ static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { int r; - assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.test", NULL) >= 0); + assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); r = sd_bus_reply_method_return(m, NULL); assert_se(r >= 0); @@ -176,7 +175,7 @@ static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_err static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { int r; - assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), m->path) >= 0); + assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a/x") >= 0); r = sd_bus_reply_method_return(m, NULL); assert_se(r >= 0); @@ -187,7 +186,7 @@ static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *er static int emit_object_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { int r; - assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), m->path) >= 0); + assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), "/value/a/x") >= 0); r = sd_bus_reply_method_return(m, NULL); assert_se(r >= 0); @@ -229,6 +228,14 @@ static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, ch return 1; } +static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + + if (object_path_startswith("/value/a", path)) + assert_se(*nodes = strv_new("/value/a/x", "/value/a/y", "/value/a/z", NULL)); + + return 1; +} + static void *server(void *p) { struct context *c = p; sd_bus *bus = NULL; @@ -247,7 +254,9 @@ static void *server(void *p) { assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0); assert_se(sd_bus_add_fallback_vtable(bus, NULL, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0); assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value", enumerator_callback, NULL) >= 0); + assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/a", enumerator2_callback, NULL) >= 0); assert_se(sd_bus_add_object_manager(bus, NULL, "/value") >= 0); + assert_se(sd_bus_add_object_manager(bus, NULL, "/value/a") >= 0); assert_se(sd_bus_start(bus) >= 0); diff --git a/src/libsystemd/sd-bus/test-bus-proxy.c b/src/libsystemd/sd-bus/test-bus-proxy.c new file mode 100644 index 0000000000..369c2f331c --- /dev/null +++ b/src/libsystemd/sd-bus/test-bus-proxy.c @@ -0,0 +1,109 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 David Herrmann <dh.herrmann@gmail.com> + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> + +#include "util.h" +#include "log.h" + +#include "sd-bus.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "bus-dump.h" + +typedef struct { + const char *sender; + int matched_acquired; +} TestProxyMatch; + +static int test_proxy_acquired(sd_bus_message *m, void *userdata, sd_bus_error *error) { + TestProxyMatch *match = userdata; + const char *name; + int r; + + r = sd_bus_message_read(m, "s", &name); + assert_se(r >= 0); + + if (!streq_ptr(match->sender, name)) + return 0; + + ++match->matched_acquired; + return 1; +} + +static void test_proxy_matched(void) { + _cleanup_bus_flush_close_unref_ sd_bus *a = NULL; + TestProxyMatch match = {}; + int r; + + /* open bus 'a' */ + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_set_address(a, "unix:path=/var/run/dbus/system_bus_socket"); + assert_se(r >= 0); + + r = sd_bus_set_bus_client(a, true); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_add_match(a, NULL, + "type='signal'," + "member='NameAcquired'", + test_proxy_acquired, &match); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &match.sender); + assert_se(r >= 0); + + /* barrier to guarantee proxy/dbus-daemon handled the previous data */ + r = sd_bus_call_method(a, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetId", + NULL, NULL, NULL); + assert_se(r >= 0); + + /* now we can be sure the Name* signals were sent */ + do { + r = sd_bus_process(a, NULL); + } while (r > 0); + assert_se(r == 0); + + assert_se(match.matched_acquired == 1); +} + +int main(int argc, char **argv) { + if (access("/var/run/dbus/system_bus_socket", F_OK) < 0) + return EXIT_TEST_SKIP; + + log_parse_environment(); + + test_proxy_matched(); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index b274f71093..7cea5a0746 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -791,6 +791,9 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { device->subsystem_set = true; } + if (!device->subsystem) + return -ENOENT; + *ret = device->subsystem; return 0; @@ -908,6 +911,9 @@ _public_ int sd_device_get_driver(sd_device *device, const char **ret) { return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); } + if (!device->driver) + return -ENOENT; + *ret = device->driver; return 0; @@ -1002,6 +1008,8 @@ _public_ int sd_device_get_sysname(sd_device *device, const char **ret) { return r; } + assert_return(device->sysname, -ENOENT); + *ret = device->sysname; return 0; diff --git a/src/libsystemd/sd-netlink/netlink-internal.h b/src/libsystemd/sd-netlink/netlink-internal.h index 6f51ebe73d..4026e2c341 100644 --- a/src/libsystemd/sd-netlink/netlink-internal.h +++ b/src/libsystemd/sd-netlink/netlink-internal.h @@ -94,6 +94,8 @@ struct sd_netlink { struct netlink_attribute { size_t offset; /* offset from hdr to attribute */ + bool nested:1; + bool net_byteorder:1; }; struct netlink_container { diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 13573dcea8..b0ed2f2882 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -38,6 +38,7 @@ #define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers ++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr; #define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) +#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) { sd_netlink_message *m; @@ -475,7 +476,7 @@ int sd_netlink_message_close_container(sd_netlink_message *m) { return 0; } -static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data) { +static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data, bool *net_byteorder) { struct netlink_attribute *attribute; struct rtattr *rta; @@ -495,6 +496,9 @@ static int netlink_message_read_internal(sd_netlink_message *m, unsigned short t *data = RTA_DATA(rta); + if (net_byteorder) + *net_byteorder = attribute->net_byteorder; + return RTA_PAYLOAD(rta); } @@ -508,7 +512,7 @@ int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, c if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, NULL); if (r < 0) return r; else if (strnlen(attr_data, r) >= (size_t) r) @@ -530,7 +534,7 @@ int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8 if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, NULL); if (r < 0) return r; else if ((size_t) r < sizeof(uint8_t)) @@ -543,8 +547,9 @@ int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8 } int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data) { - int r; void *attr_data; + bool net_byteorder; + int r; assert_return(m, -EINVAL); @@ -552,21 +557,26 @@ int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); if (r < 0) return r; else if ((size_t) r < sizeof(uint16_t)) return -EIO; - if (data) - *data = *(uint16_t *) attr_data; + if (data) { + if (net_byteorder) + *data = be16toh(*(uint16_t *) attr_data); + else + *data = *(uint16_t *) attr_data; + } return 0; } int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data) { - int r; void *attr_data; + bool net_byteorder; + int r; assert_return(m, -EINVAL); @@ -574,14 +584,18 @@ int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); if (r < 0) return r; else if ((size_t)r < sizeof(uint32_t)) return -EIO; - if (data) - *data = *(uint32_t *) attr_data; + if (data) { + if (net_byteorder) + *data = be32toh(*(uint32_t *) attr_data); + else + *data = *(uint32_t *) attr_data; + } return 0; } @@ -596,7 +610,7 @@ int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short typ if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, NULL); if (r < 0) return r; else if ((size_t)r < sizeof(struct ether_addr)) @@ -618,7 +632,7 @@ int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short typ if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, NULL); if (r < 0) return r; else if ((size_t)r < sizeof(struct ifa_cacheinfo)) @@ -640,7 +654,7 @@ int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, NULL); if (r < 0) return r; else if ((size_t)r < sizeof(struct in_addr)) @@ -662,7 +676,7 @@ int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, if (r < 0) return r; - r = netlink_message_read_internal(m, type, &attr_data); + r = netlink_message_read_internal(m, type, &attr_data, NULL); if (r < 0) return r; else if ((size_t)r < sizeof(struct in6_addr)) @@ -699,6 +713,8 @@ static int netlink_container_parse(sd_netlink_message *m, log_debug("rtnl: message parse - overwriting repeated attribute"); attributes[type].offset = (uint8_t *) rta - (uint8_t *) m->hdr; + attributes[type].nested = RTA_FLAGS(rta) & NLA_F_NESTED; + attributes[type].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; } container->attributes = attributes; @@ -781,7 +797,7 @@ int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short typ } else return -EINVAL; - r = netlink_message_read_internal(m, type_id, &container); + r = netlink_message_read_internal(m, type_id, &container, NULL); if (r < 0) return r; else diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 74ac2ab344..1e747abb24 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -196,27 +196,37 @@ static const NLType rtnl_link_info_data_iptun_types[IFLA_IPTUN_MAX + 1] = { [IFLA_IPTUN_6RD_RELAY_PREFIX] = { .type = NETLINK_TYPE_U32 }, [IFLA_IPTUN_6RD_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, }; static const NLType rtnl_link_info_data_ipgre_types[IFLA_GRE_MAX + 1] = { - [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, }; static const NLType rtnl_link_info_data_ipvti_types[IFLA_VTI_MAX + 1] = { [IFLA_VTI_LINK] = { .type = NETLINK_TYPE_U32 }, [IFLA_VTI_IKEY] = { .type = NETLINK_TYPE_U32 }, [IFLA_VTI_OKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, }; static const NLType rtnl_link_info_data_ip6tnl_types[IFLA_IPTUN_MAX + 1] = { @@ -227,7 +237,7 @@ static const NLType rtnl_link_info_data_ip6tnl_types[IFLA_IPTUN_MAX + 1] = { [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U32 }, [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLOWINFO] = { .type = NETLINK_TYPE_U32}, + [IFLA_IPTUN_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, }; /* these strings must match the .kind entries in the kernel */ @@ -238,6 +248,7 @@ static const char* const nl_union_link_info_data_table[_NL_UNION_LINK_INFO_DATA_ [NL_UNION_LINK_INFO_DATA_VETH] = "veth", [NL_UNION_LINK_INFO_DATA_DUMMY] = "dummy", [NL_UNION_LINK_INFO_DATA_MACVLAN] = "macvlan", + [NL_UNION_LINK_INFO_DATA_MACVTAP] = "macvtap", [NL_UNION_LINK_INFO_DATA_IPVLAN] = "ipvlan", [NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan", [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip", @@ -264,6 +275,8 @@ static const NLTypeSystem rtnl_link_info_data_type_systems[_NL_UNION_LINK_INFO_D .types = rtnl_link_info_data_veth_types }, [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), .types = rtnl_link_info_data_ipvlan_types }, [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), @@ -319,8 +332,11 @@ static const struct NLType rtnl_prot_info_bridge_port_types[IFLA_BRPORT_MAX + 1] [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, }; static const NLTypeSystem rtnl_prot_info_type_systems[AF_MAX] = { @@ -362,9 +378,9 @@ static const NLTypeSystem rtnl_af_spec_type_system = { }; static const NLType rtnl_link_types[IFLA_MAX + 1 ] = { - [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR, }, - [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR, }, - [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1, }, + [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, [IFLA_MTU] = { .type = NETLINK_TYPE_U32 }, [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, /* diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index a210163241..758ffad1b7 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -73,6 +73,7 @@ typedef enum NLUnionLinkInfoData { NL_UNION_LINK_INFO_DATA_VETH, NL_UNION_LINK_INFO_DATA_DUMMY, NL_UNION_LINK_INFO_DATA_MACVLAN, + NL_UNION_LINK_INFO_DATA_MACVTAP, NL_UNION_LINK_INFO_DATA_IPVLAN, NL_UNION_LINK_INFO_DATA_VXLAN, NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL, diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 82654ee8c7..e6371ff04d 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -699,9 +699,12 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus * after the user-session and want the user-session to take * over the VT. We need to support this for * backwards-compatibility, so make sure we allow new sessions - * on a VT that a greeter is running on. + * on a VT that a greeter is running on. Furthermore, to allow + * re-logins, we have to allow a greeter to take over a used VT for + * the exact same reasons. */ - if (vtnr > 0 && + if (c != SESSION_GREETER && + vtnr > 0 && vtnr < m->seat0->position_count && m->seat0->positions[vtnr] && m->seat0->positions[vtnr]->class != SESSION_GREETER) @@ -1172,7 +1175,7 @@ static int trigger_device(Manager *m, struct udev_device *d) { if (!t) return -ENOMEM; - write_string_file(t, "change"); + write_string_file(t, "change", WRITE_STRING_FILE_CREATE); } return 0; @@ -1771,7 +1774,7 @@ static int nologin_timeout_handler( log_info("Creating /run/nologin, blocking further logins..."); - r = write_string_file_atomic("/run/nologin", "System is going down."); + r = write_string_file("/run/nologin", "System is going down.", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); if (r < 0) log_error_errno(r, "Failed to create /run/nologin: %m"); else @@ -2446,8 +2449,6 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_METHOD("PowerOff", "b", NULL, method_poweroff, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Reboot", "b", NULL, method_reboot, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Suspend", "b", NULL, method_suspend, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ScheduleShutdown", "st", NULL, method_schedule_shutdown, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CancelScheduledShutdown", NULL, "b", method_cancel_scheduled_shutdown, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Hibernate", "b", NULL, method_hibernate, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("HybridSleep", "b", NULL, method_hybrid_sleep, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CanPowerOff", NULL, "s", method_can_poweroff, SD_BUS_VTABLE_UNPRIVILEGED), @@ -2455,6 +2456,8 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_METHOD("CanSuspend", NULL, "s", method_can_suspend, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CanHibernate", NULL, "s", method_can_hibernate, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CanHybridSleep", NULL, "s", method_can_hybrid_sleep, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ScheduleShutdown", "st", NULL, method_schedule_shutdown, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CancelScheduledShutdown", NULL, "b", method_cancel_scheduled_shutdown, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("Inhibit", "ssss", "h", method_inhibit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("CanRebootToFirmwareSetup", NULL, "s", method_can_reboot_to_firmware_setup, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetRebootToFirmwareSetup", "b", NULL, method_set_reboot_to_firmware_setup, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index fb5d076311..495ec50be0 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -290,8 +290,8 @@ int seat_switch_to_next(Seat *s) { return -EINVAL; start = 1; - if (s->active && s->active->pos > 0) - start = s->active->pos; + if (s->active && s->active->position > 0) + start = s->active->position; for (i = start + 1; i < s->position_count; ++i) if (s->positions[i]) @@ -311,8 +311,8 @@ int seat_switch_to_previous(Seat *s) { return -EINVAL; start = 1; - if (s->active && s->active->pos > 0) - start = s->active->pos; + if (s->active && s->active->position > 0) + start = s->active->position; for (i = start - 1; i > 0; --i) if (s->positions[i]) @@ -472,9 +472,9 @@ int seat_stop_sessions(Seat *s, bool force) { void seat_evict_position(Seat *s, Session *session) { Session *iter; - unsigned int pos = session->pos; + unsigned int pos = session->position; - session->pos = 0; + session->position = 0; if (pos == 0) return; @@ -486,7 +486,7 @@ void seat_evict_position(Seat *s, Session *session) { * position (eg., during gdm->session transition), so let's look * for it and set it on the free slot. */ LIST_FOREACH(sessions_by_seat, iter, s->sessions) { - if (iter->pos == pos) { + if (iter->position == pos && session_get_state(iter) != SESSION_CLOSING) { s->positions[pos] = iter; break; } @@ -504,15 +504,15 @@ void seat_claim_position(Seat *s, Session *session, unsigned int pos) { seat_evict_position(s, session); - session->pos = pos; - if (pos > 0 && !s->positions[pos]) + session->position = pos; + if (pos > 0) s->positions[pos] = session; } static void seat_assign_position(Seat *s, Session *session) { unsigned int pos; - if (session->pos > 0) + if (session->position > 0) return; for (pos = 1; pos < s->position_count; ++pos) diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 6a450b02a0..45f4c09d3d 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -264,7 +264,7 @@ int session_save(Session *s) { fprintf(f, "VTNR=%u\n", s->vtnr); if (!s->vtnr) - fprintf(f, "POS=%u\n", s->pos); + fprintf(f, "POSITION=%u\n", s->position); if (s->leader > 0) fprintf(f, "LEADER="PID_FMT"\n", s->leader); @@ -302,7 +302,7 @@ int session_load(Session *s) { *seat = NULL, *vtnr = NULL, *state = NULL, - *pos = NULL, + *position = NULL, *leader = NULL, *type = NULL, *class = NULL, @@ -329,7 +329,7 @@ int session_load(Session *s) { "DESKTOP", &s->desktop, "VTNR", &vtnr, "STATE", &state, - "POS", &pos, + "POSITION", &position, "LEADER", &leader, "TYPE", &type, "CLASS", &class, @@ -388,10 +388,10 @@ int session_load(Session *s) { if (!s->seat || !seat_has_vts(s->seat)) s->vtnr = 0; - if (pos && s->seat) { + if (position && s->seat) { unsigned int npos; - safe_atou(pos, &npos); + safe_atou(position, &npos); seat_claim_position(s->seat, s, npos); } diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 4bf739a44d..b8565ebf51 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -70,7 +70,7 @@ struct Session { Manager *manager; const char *id; - unsigned int pos; + unsigned int position; SessionType type; SessionClass class; diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c index 0f72d70b10..36c0e8626d 100644 --- a/src/login/logind-user-dbus.c +++ b/src/login/logind-user-dbus.c @@ -103,11 +103,7 @@ static int property_get_sessions( } - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 1; + return sd_bus_message_close_container(reply); } static int property_get_idle_hint( diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf index 0ad78802dd..d8deb7bc8b 100644 --- a/src/login/org.freedesktop.login1.conf +++ b/src/login/org.freedesktop.login1.conf @@ -90,6 +90,42 @@ <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Manager" + send_member="LockSession"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="UnlockSession"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="LockSessions"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="UnlockSessions"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="KillSession"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="KillUser"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="TerminateSession"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="TerminateUser"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="TerminateSeat"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" send_member="PowerOff"/> <allow send_destination="org.freedesktop.login1" @@ -130,6 +166,14 @@ <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Manager" + send_member="ScheduleShutdown"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" + send_member="CancelScheduledShutdown"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Manager" send_member="CanRebootToFirmwareSetup"/> <allow send_destination="org.freedesktop.login1" @@ -146,6 +190,10 @@ <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Seat" + send_member="Terminate"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Seat" send_member="ActivateSession"/> <allow send_destination="org.freedesktop.login1" @@ -162,14 +210,30 @@ <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Session" + send_member="Terminate"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Session" send_member="Activate"/> <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Session" + send_member="Lock"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Session" + send_member="Unlock"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Session" send_member="SetIdleHint"/> <allow send_destination="org.freedesktop.login1" send_interface="org.freedesktop.login1.Session" + send_member="Kill"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.Session" send_member="TakeControl"/> <allow send_destination="org.freedesktop.login1" @@ -188,6 +252,14 @@ send_interface="org.freedesktop.login1.Session" send_member="PauseDeviceComplete"/> + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.User" + send_member="Terminate"/> + + <allow send_destination="org.freedesktop.login1" + send_interface="org.freedesktop.login1.User" + send_member="Kill"/> + <allow receive_sender="org.freedesktop.login1"/> </policy> diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 7813a0bcc7..dc42ffdc52 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -55,17 +55,12 @@ static int property_get_id( sd_bus_error *error) { Machine *m = userdata; - int r; assert(bus); assert(reply); assert(m); - r = sd_bus_message_append_array(reply, 'y', &m->id, 16); - if (r < 0) - return r; - - return 1; + return sd_bus_message_append_array(reply, 'y', &m->id, 16); } static int property_get_state( @@ -104,7 +99,6 @@ static int property_get_netif( sd_bus_error *error) { Machine *m = userdata; - int r; assert(bus); assert(reply); @@ -112,11 +106,7 @@ static int property_get_netif( assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); - if (r < 0) - return r; - - return 1; + return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); } static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 0e971a6789..3637815fc9 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -31,12 +31,13 @@ #include "bus-common-errors.h" #include "cgroup-util.h" #include "btrfs-util.h" +#include "formats-util.h" +#include "process-util.h" #include "machine-image.h" #include "machine-pool.h" #include "image-dbus.h" #include "machined.h" #include "machine-dbus.h" -#include "formats-util.h" static int property_get_pool_path( sd_bus *bus, @@ -840,6 +841,230 @@ static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bu return bus_image_method_set_limit(message, i, error); } +static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + const char *name, *p; + Machine *machine; + uint32_t uid; + int r; + + r = sd_bus_message_read(message, "su", &name, &uid); + if (r < 0) + return r; + + if (UID_IS_INVALID(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + p = procfs_file_alloca(machine->leader, "uid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno != 0) + return -errno; + + return -EIO; + } + + if (uid < uid_base || uid >= uid_base + uid_range) + continue; + + converted = uid - uid_base + uid_shift; + if (UID_IS_INVALID(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); +} + +static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + uid_t uid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &uid); + if (r < 0) + return r; + if (UID_IS_INVALID(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + if (uid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno != 0) + return -errno; + + return -EIO; + } + + if (uid < uid_shift || uid >= uid_shift + uid_range) + continue; + + converted = (uid - uid_shift + uid_base); + if (UID_IS_INVALID(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); +} + +static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = groupdata; + const char *name, *p; + Machine *machine; + uint32_t gid; + int r; + + r = sd_bus_message_read(message, "su", &name, &gid); + if (r < 0) + return r; + + if (GID_IS_INVALID(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + p = procfs_file_alloca(machine->leader, "gid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno != 0) + return -errno; + + return -EIO; + } + + if (gid < gid_base || gid >= gid_base + gid_range) + continue; + + converted = gid - gid_base + gid_shift; + if (GID_IS_INVALID(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); +} + +static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + Manager *m = groupdata; + Machine *machine; + gid_t gid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &gid); + if (r < 0) + return r; + if (GID_IS_INVALID(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + if (gid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno != 0) + return -errno; + + return -EIO; + } + + if (gid < gid_shift || gid >= gid_shift + gid_range) + continue; + + converted = (gid - gid_shift + gid_base); + if (GID_IS_INVALID(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); +} + const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), @@ -869,6 +1094,10 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("MachineNew", "so", 0), SD_BUS_SIGNAL("MachineRemoved", "so", 0), SD_BUS_VTABLE_END diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf index 93aaf6a377..d58f01507b 100644 --- a/src/machine/org.freedesktop.machine1.conf +++ b/src/machine/org.freedesktop.machine1.conf @@ -113,6 +113,22 @@ send_member="SetImageLimit"/> <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapFromMachineUser"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapToMachineUser"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapFromMachineGroup"/> + + <allow send_destination="org.freedesktop.machine1" + send_interface="org.freedesktop.machine1.Manager" + send_member="MapToMachineGroup"/> + + <allow send_destination="org.freedesktop.machine1" send_interface="org.freedesktop.machine1.Machine" send_member="GetAddresses"/> diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index d446bfa8b3..4aa301b112 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -270,12 +270,18 @@ static int dhcp_lease_lost(Link *link) { if (link->network->dhcp_hostname) { const char *hostname = NULL; - r = sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname); - if (r >= 0 && hostname) { - r = link_set_hostname(link, ""); + if (!link->network->hostname) + r = sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname); + else + hostname = link->network->hostname; + + if (r >= 0 || hostname) { + r = link_set_hostname(link, hostname); if (r < 0) - log_link_error(link, - "Failed to reset transient hostname"); + log_link_error_errno(link, r, + "Failed to set transient hostname to '%s': %m", + hostname); + } } @@ -464,8 +470,12 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) { if (link->network->dhcp_hostname) { const char *hostname; - r = sd_dhcp_lease_get_hostname(lease, &hostname); - if (r >= 0) { + if (!link->network->hostname) + r = sd_dhcp_lease_get_hostname(lease, &hostname); + else + hostname = link->network->hostname; + + if (r >= 0 || hostname) { r = link_set_hostname(link, hostname); if (r < 0) log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname); @@ -616,14 +626,19 @@ int dhcp4_configure(Link *link) { if (link->network->dhcp_sendhost) { _cleanup_free_ char *hostname = NULL; + const char *hn = NULL; + + if (!link->network->hostname) { + hostname = gethostname_malloc(); + if (!hostname) + return -ENOMEM; - hostname = gethostname_malloc(); - if (!hostname) - return -ENOMEM; + hn = hostname; + } else + hn = link->network->hostname; - if (!is_localhost(hostname)) { - r = sd_dhcp_client_set_hostname(link->dhcp_client, - hostname); + if (!is_localhost(hn)) { + r = sd_dhcp_client_set_hostname(link->dhcp_client, hn); if (r < 0) return r; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 5607cf470e..f20f68b482 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -846,9 +846,6 @@ static int link_set_bridge(Link *link) { assert(link); assert(link->network); - if(link->network->cost == 0) - return 0; - r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); if (r < 0) return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); @@ -861,6 +858,26 @@ static int link_set_bridge(Link *link) { if (r < 0) return log_link_error_errno(link, r, "Could not append IFLA_PROTINFO attribute: %m"); + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_GUARD, !link->network->use_bpdu); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_GUARD attribute: %m"); + + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MODE, link->network->hairpin); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MODE attribute: %m"); + + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_FAST_LEAVE, link->network->fast_leave); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_FAST_LEAVE attribute: %m"); + + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROTECT, !link->network->allow_port_to_be_root); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROTECT attribute: %m"); + + r = sd_netlink_message_append_u8(req, IFLA_BRPORT_UNICAST_FLOOD, link->network->unicast_flood); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_UNICAST_FLOOD attribute: %m"); + if(link->network->cost != 0) { r = sd_netlink_message_append_u32(req, IFLA_BRPORT_COST, link->network->cost); if (r < 0) @@ -1495,7 +1512,7 @@ static int link_set_ipv4_forward(Link *link) { p = strjoina("/proc/sys/net/ipv4/conf/", link->ifname, "/forwarding"); v = one_zero(link_ipv4_forward_enabled(link)); - r = write_string_file_no_create(p, v); + r = write_string_file(p, v, 0); if (r < 0) { /* If the right value is set anyway, don't complain */ if (verify_one_line_file(p, v) > 0) @@ -1524,7 +1541,7 @@ static int link_set_ipv6_forward(Link *link) { p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/forwarding"); v = one_zero(link_ipv6_forward_enabled(link)); - r = write_string_file_no_create(p, v); + r = write_string_file(p, v, 0); if (r < 0) { /* If the right value is set anyway, don't complain */ if (verify_one_line_file(p, v) > 0) @@ -1553,7 +1570,7 @@ static int link_set_ipv6_privacy_extensions(Link *link) { p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/use_tempaddr"); xsprintf(buf, "%u", link->network->ipv6_privacy_extensions); - r = write_string_file_no_create(p, buf); + r = write_string_file(p, buf, 0); if (r < 0) { /* If the right value is set anyway, don't complain */ if (verify_one_line_file(p, buf) > 0) diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf index 66ed2e013c..7e46293a06 100644 --- a/src/network/networkd-netdev-gperf.gperf +++ b/src/network/networkd-netdev-gperf.gperf @@ -29,6 +29,7 @@ NetDev.MTUBytes, config_parse_iec_size, 0, NetDev.MACAddress, config_parse_hwaddr, 0, offsetof(NetDev, mac) VLAN.Id, config_parse_uint64, 0, offsetof(VLan, id) MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) +MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) Tunnel.Local, config_parse_tunnel_address, 0, offsetof(Tunnel, local) Tunnel.Remote, config_parse_tunnel_address, 0, offsetof(Tunnel, remote) @@ -36,6 +37,8 @@ Tunnel.TOS, config_parse_unsigned, 0, Tunnel.TTL, config_parse_unsigned, 0, offsetof(Tunnel, ttl) Tunnel.DiscoverPathMTU, config_parse_bool, 0, offsetof(Tunnel, pmtudisc) Tunnel.Mode, config_parse_ip6tnl_mode, 0, offsetof(Tunnel, ip6tnl_mode) +Tunnel.IPv6FlowLabel, config_parse_ipv6_flowlabel, 0, offsetof(Tunnel, ipv6_flowlabel) +Tunnel.CopyDSCP, config_parse_bool, 0, offsetof(Tunnel, copy_dscp) Peer.Name, config_parse_ifname, 0, offsetof(Veth, ifname_peer) Peer.MACAddress, config_parse_hwaddr, 0, offsetof(Veth, mac_peer) VXLAN.Id, config_parse_uint64, 0, offsetof(VxLan, id) @@ -59,6 +62,7 @@ Tun.Group, config_parse_string, 0, Tap.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue) Tap.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) Tap.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) +Tap.VNetHeader, config_parse_bool, 0, offsetof(TunTap, vnet_hdr) Tap.User, config_parse_string, 0, offsetof(TunTap, user_name) Tap.Group, config_parse_string, 0, offsetof(TunTap, group_name) Bond.Mode, config_parse_bond_mode, 0, offsetof(Bond, mode) diff --git a/src/network/networkd-netdev-macvlan.c b/src/network/networkd-netdev-macvlan.c index c2c564935c..e17de793ce 100644 --- a/src/network/networkd-netdev-macvlan.c +++ b/src/network/networkd-netdev-macvlan.c @@ -35,14 +35,20 @@ DEFINE_STRING_TABLE_LOOKUP(macvlan_mode, MacVlanMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_macvlan_mode, macvlan_mode, MacVlanMode, "Failed to parse macvlan mode"); static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { - MacVlan *m = MACVLAN(netdev); + MacVlan *m; int r; assert(netdev); - assert(m); assert(link); assert(netdev->ifname); + if (netdev->kind == NETDEV_KIND_MACVLAN) + m = MACVLAN(netdev); + else + m = MACVTAP(netdev); + + assert(m); + if (m->mode != _NETDEV_MACVLAN_MODE_INVALID) { r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MODE, m->mode); if (r < 0) @@ -53,14 +59,28 @@ static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_net } static void macvlan_init(NetDev *n) { - MacVlan *m = MACVLAN(n); + MacVlan *m; assert(n); + + if (n->kind == NETDEV_KIND_MACVLAN) + m = MACVLAN(n); + else + m = MACVTAP(n); + assert(m); m->mode = _NETDEV_MACVLAN_MODE_INVALID; } +const NetDevVTable macvtap_vtable = { + .object_size = sizeof(MacVlan), + .init = macvlan_init, + .sections = "Match\0NetDev\0MACVTAP\0", + .fill_message_create = netdev_macvlan_fill_message_create, + .create_type = NETDEV_CREATE_STACKED, +}; + const NetDevVTable macvlan_vtable = { .object_size = sizeof(MacVlan), .init = macvlan_init, diff --git a/src/network/networkd-netdev-macvlan.h b/src/network/networkd-netdev-macvlan.h index d61efc16d4..c491bfa312 100644 --- a/src/network/networkd-netdev-macvlan.h +++ b/src/network/networkd-netdev-macvlan.h @@ -41,6 +41,7 @@ struct MacVlan { }; extern const NetDevVTable macvlan_vtable; +extern const NetDevVTable macvtap_vtable; const char *macvlan_mode_to_string(MacVlanMode d) _const_; MacVlanMode macvlan_mode_from_string(const char *d) _pure_; diff --git a/src/network/networkd-netdev-tunnel.c b/src/network/networkd-netdev-tunnel.c index 5533fb5c7b..7fd9ef584b 100644 --- a/src/network/networkd-netdev-tunnel.c +++ b/src/network/networkd-netdev-tunnel.c @@ -33,6 +33,7 @@ #include "conf-parser.h" #define DEFAULT_TNL_HOP_LIMIT 64 +#define IP6_FLOWINFO_FLOWLABEL htonl(0x000FFFFF) static const char* const ip6tnl_mode_table[_NETDEV_IP6_TNL_MODE_MAX] = { [NETDEV_IP6_TNL_MODE_IP6IP6] = "ip6ip6", @@ -184,6 +185,16 @@ static int netdev_ip6gre_fill_message_create(NetDev *netdev, Link *link, sd_netl if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TTL attribute: %m"); + if (t->ipv6_flowlabel != _NETDEV_IPV6_FLOWLABEL_INVALID) { + r = sd_netlink_message_append_u32(m, IFLA_GRE_FLOWINFO, t->ipv6_flowlabel); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_FLOWINFO attribute: %m"); + } + + r = sd_netlink_message_append_u32(m, IFLA_GRE_FLAGS, t->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_FLAGS attribute: %m"); + return r; } @@ -264,6 +275,19 @@ static int netdev_ip6tnl_fill_message_create(NetDev *netdev, Link *link, sd_netl if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_TTL attribute: %m"); + if (t->ipv6_flowlabel != _NETDEV_IPV6_FLOWLABEL_INVALID) { + r = sd_netlink_message_append_u32(m, IFLA_IPTUN_FLOWINFO, t->ipv6_flowlabel); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLOWINFO attribute: %m"); + } + + if (t->copy_dscp) + t->flags |= IP6_TNL_F_RCV_DSCP_COPY; + + r = sd_netlink_message_append_u32(m, IFLA_IPTUN_FLAGS, t->flags); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLAGS attribute: %m"); + switch (t->ip6tnl_mode) { case NETDEV_IP6_TNL_MODE_IP6IP6: proto = IPPROTO_IPV6; @@ -380,6 +404,52 @@ int config_parse_tunnel_address(const char *unit, return 0; } +static const char* const ipv6_flowlabel_table[_NETDEV_IPV6_FLOWLABEL_MAX] = { + [NETDEV_IPV6_FLOWLABEL_INHERIT] = "inherit", +}; + +DEFINE_STRING_TABLE_LOOKUP(ipv6_flowlabel, IPv6FlowLabel); + +int config_parse_ipv6_flowlabel(const char* unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + IPv6FlowLabel *ipv6_flowlabel = data; + Tunnel *t = userdata; + IPv6FlowLabel s; + int k = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(ipv6_flowlabel); + + s = ipv6_flowlabel_from_string(rvalue); + if (s != _NETDEV_IPV6_FLOWLABEL_INVALID) { + *ipv6_flowlabel = IP6_FLOWINFO_FLOWLABEL; + t->flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL; + } else { + r = config_parse_unsigned(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &k, userdata); + if (r >= 0) { + if (k > 0xFFFFF) + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse IPv6 flowlabel option, ignoring: %s", rvalue); + else { + *ipv6_flowlabel = htonl(k) & IP6_FLOWINFO_FLOWLABEL; + t->flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL; + } + } + } + + return 0; +} + static void ipip_init(NetDev *n) { Tunnel *t = IPIP(n); @@ -452,6 +522,7 @@ static void ip6tnl_init(NetDev *n) { t->ttl = DEFAULT_TNL_HOP_LIMIT; t->encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT; t->ip6tnl_mode = _NETDEV_IP6_TNL_MODE_INVALID; + t->ipv6_flowlabel = _NETDEV_IPV6_FLOWLABEL_INVALID; } const NetDevVTable ipip_vtable = { diff --git a/src/network/networkd-netdev-tunnel.h b/src/network/networkd-netdev-tunnel.h index 88f57ac105..1fd2b94ae1 100644 --- a/src/network/networkd-netdev-tunnel.h +++ b/src/network/networkd-netdev-tunnel.h @@ -33,6 +33,12 @@ typedef enum Ip6TnlMode { _NETDEV_IP6_TNL_MODE_INVALID = -1, } Ip6TnlMode; +typedef enum IPv6FlowLabel { + NETDEV_IPV6_FLOWLABEL_INHERIT = 0xFFFFF + 1, + _NETDEV_IPV6_FLOWLABEL_MAX, + _NETDEV_IPV6_FLOWLABEL_INVALID = -1, +} IPv6FlowLabel; + struct Tunnel { NetDev meta; @@ -48,8 +54,10 @@ struct Tunnel { union in_addr_union remote; Ip6TnlMode ip6tnl_mode; + IPv6FlowLabel ipv6_flowlabel; bool pmtudisc; + bool copy_dscp; }; extern const NetDevVTable ipip_vtable; @@ -70,3 +78,23 @@ int config_parse_ip6tnl_mode(const char *unit, const char *filename, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +int config_parse_tunnel_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); + +const char *ipv6_flowlabel_to_string(IPv6FlowLabel d) _const_; +IPv6FlowLabel ipv6_flowlabel_from_string(const char *d) _pure_; + +int config_parse_ipv6_flowlabel(const char *unit, const char *filename, + unsigned line, const char *section, + unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, + void *userdata); diff --git a/src/network/networkd-netdev-tuntap.c b/src/network/networkd-netdev-tuntap.c index 378312f091..ba84e802fc 100644 --- a/src/network/networkd-netdev-tuntap.c +++ b/src/network/networkd-netdev-tuntap.c @@ -51,6 +51,9 @@ static int netdev_fill_tuntap_message(NetDev *netdev, struct ifreq *ifr) { if (t->multi_queue) ifr->ifr_flags |= IFF_MULTI_QUEUE; + if (t->vnet_hdr) + ifr->ifr_flags |= IFF_VNET_HDR; + strncpy(ifr->ifr_name, netdev->ifname, IFNAMSIZ-1); return 0; diff --git a/src/network/networkd-netdev-tuntap.h b/src/network/networkd-netdev-tuntap.h index b804875bbb..29f8bb0ea5 100644 --- a/src/network/networkd-netdev-tuntap.h +++ b/src/network/networkd-netdev-tuntap.h @@ -33,6 +33,7 @@ struct TunTap { bool one_queue; bool multi_queue; bool packet_info; + bool vnet_hdr; }; extern const NetDevVTable tun_vtable; diff --git a/src/network/networkd-netdev-vxlan.h b/src/network/networkd-netdev-vxlan.h index fe5254e91f..e7d1306f13 100644 --- a/src/network/networkd-netdev-vxlan.h +++ b/src/network/networkd-netdev-vxlan.h @@ -53,3 +53,14 @@ struct VxLan { }; extern const NetDevVTable vxlan_vtable; + +int config_parse_vxlan_group_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); diff --git a/src/network/networkd-netdev.c b/src/network/networkd-netdev.c index 6949b403c8..cd31387b41 100644 --- a/src/network/networkd-netdev.c +++ b/src/network/networkd-netdev.c @@ -34,6 +34,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = { [NETDEV_KIND_BOND] = &bond_vtable, [NETDEV_KIND_VLAN] = &vlan_vtable, [NETDEV_KIND_MACVLAN] = &macvlan_vtable, + [NETDEV_KIND_MACVTAP] = &macvtap_vtable, [NETDEV_KIND_IPVLAN] = &ipvlan_vtable, [NETDEV_KIND_VXLAN] = &vxlan_vtable, [NETDEV_KIND_IPIP] = &ipip_vtable, @@ -56,6 +57,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = { [NETDEV_KIND_BOND] = "bond", [NETDEV_KIND_VLAN] = "vlan", [NETDEV_KIND_MACVLAN] = "macvlan", + [NETDEV_KIND_MACVTAP] = "macvtap", [NETDEV_KIND_IPVLAN] = "ipvlan", [NETDEV_KIND_VXLAN] = "vxlan", [NETDEV_KIND_IPIP] = "ipip", diff --git a/src/network/networkd-netdev.h b/src/network/networkd-netdev.h index a004f2fe5f..19fb5bb185 100644 --- a/src/network/networkd-netdev.h +++ b/src/network/networkd-netdev.h @@ -40,6 +40,7 @@ typedef enum NetDevKind { NETDEV_KIND_BOND, NETDEV_KIND_VLAN, NETDEV_KIND_MACVLAN, + NETDEV_KIND_MACVTAP, NETDEV_KIND_IPVLAN, NETDEV_KIND_VXLAN, NETDEV_KIND_IPIP, @@ -161,6 +162,7 @@ DEFINE_CAST(BRIDGE, Bridge); DEFINE_CAST(BOND, Bond); DEFINE_CAST(VLAN, VLan); DEFINE_CAST(MACVLAN, MacVlan); +DEFINE_CAST(MACVTAP, MacVlan); DEFINE_CAST(IPVLAN, IPVlan); DEFINE_CAST(VXLAN, VxLan); DEFINE_CAST(IPIP, Tunnel); diff --git a/src/network/networkd-network-bus.c b/src/network/networkd-network-bus.c index b5f8f5cfb2..5717a15327 100644 --- a/src/network/networkd-network-bus.c +++ b/src/network/networkd-network-bus.c @@ -53,11 +53,7 @@ static int property_get_ether_addrs( return r; } - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 1; + return sd_bus_message_close_container(reply); } const sd_bus_vtable network_vtable[] = { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 787fc2ff5b..8735b39581 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -31,6 +31,7 @@ Network.Bridge, config_parse_netdev, 0 Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) Network.VLAN, config_parse_netdev, 0, 0 Network.MACVLAN, config_parse_netdev, 0, 0 +Network.MACVTAP, config_parse_netdev, 0, 0 Network.IPVLAN, config_parse_netdev, 0, 0 Network.VXLAN, config_parse_netdev, 0, 0 Network.Tunnel, config_parse_tunnel, 0, 0 @@ -67,11 +68,17 @@ DHCP.UseHostname, config_parse_bool, 0 DHCP.UseDomains, config_parse_bool, 0, offsetof(Network, dhcp_domains) DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_routes) DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_sendhost) +DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, hostname) DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) Bridge.Cost, config_parse_unsigned, 0, offsetof(Network, cost) +Bridge.UseBPDU, config_parse_bool, 0, offsetof(Network, use_bpdu) +Bridge.HairPin, config_parse_bool, 0, offsetof(Network, hairpin) +Bridge.FastLeave, config_parse_bool, 0, offsetof(Network, fast_leave) +Bridge.AllowPortToBeRoot, config_parse_bool, 0, offsetof(Network, allow_port_to_be_root) +Bridge.UnicastFlood, config_parse_bool, 0, offsetof(Network, unicast_flood) BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0 BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 /* backwards compatibility: do not add new entries to this section */ diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index a8e9ef909c..e3593fc0ea 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -107,6 +107,10 @@ static int network_load_one(Manager *manager, const char *filename) { network->dhcp_route_metric = DHCP_ROUTE_METRIC; network->dhcp_client_identifier = DHCP_CLIENT_ID_DUID; + network->use_bpdu = true; + network->allow_port_to_be_root = true; + network->unicast_flood = true; + network->llmnr = LLMNR_SUPPORT_YES; network->link_local = ADDRESS_FAMILY_IPV6; @@ -207,6 +211,7 @@ void network_free(Network *network) { free(network->description); free(network->dhcp_vendor_class_identifier); + free(network->hostname); free(network->mac); @@ -423,6 +428,7 @@ int config_parse_netdev(const char *unit, break; case NETDEV_KIND_VLAN: case NETDEV_KIND_MACVLAN: + case NETDEV_KIND_MACVTAP: case NETDEV_KIND_IPVLAN: case NETDEV_KIND_VXLAN: r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev); @@ -809,3 +815,38 @@ int config_parse_ipv6_privacy_extensions( return 0; } + +int config_parse_hostname(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + char **hostname = data; + char *hn = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = config_parse_string(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, &hn, userdata); + if (r < 0) + return r; + + if (!hostname_is_valid(hn)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, "hostname is not valid, ignoring assignment: %s", rvalue); + + free(hn); + return 0; + } + + *hostname = hn; + + return 0; +} diff --git a/src/network/networkd-wait-online.c b/src/network/networkd-wait-online.c index 32c31fdf3d..d958b48771 100644 --- a/src/network/networkd-wait-online.c +++ b/src/network/networkd-wait-online.c @@ -66,7 +66,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hiq", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hi:q", options, NULL)) >= 0) switch (c) { diff --git a/src/network/networkd.h b/src/network/networkd.h index f98c640822..a285a4b08f 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -133,6 +133,7 @@ struct Network { AddressFamilyBoolean dhcp; DCHPClientIdentifier dhcp_client_identifier; char *dhcp_vendor_class_identifier; + char *hostname; bool dhcp_dns; bool dhcp_ntp; bool dhcp_mtu; @@ -149,6 +150,11 @@ struct Network { bool dhcp_server; + bool use_bpdu; + bool hairpin; + bool fast_leave; + bool allow_port_to_be_root; + bool unicast_flood; unsigned cost; AddressFamilyBoolean ip_forward; @@ -319,28 +325,6 @@ int config_parse_tunnel(const char *unit, void *data, void *userdata); -int config_parse_tunnel_address(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata); - -int config_parse_vxlan_group_address(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata); - extern const sd_bus_vtable network_vtable[]; int network_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); @@ -473,3 +457,7 @@ const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; int config_parse_ipv6_privacy_extensions(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + + +/* Hostname */ +int config_parse_hostname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 198de3097d..65b9a5071b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1702,7 +1702,7 @@ static int setup_boot_id(const char *dest) { id128_format_as_uuid(rnd, as_uuid); - r = write_string_file(from, as_uuid); + r = write_string_file(from, as_uuid, WRITE_STRING_FILE_CREATE); if (r < 0) return log_error_errno(r, "Failed to write boot id: %m"); @@ -1785,15 +1785,13 @@ static int setup_pts(const char *dest) { #ifdef HAVE_SELINUX if (arg_selinux_apifs_context) (void) asprintf(&options, - "newinstance,ptmxmode=0666,mode=620,uid=" UID_FMT ",gid=" GID_FMT ",context=\"%s\"", - arg_uid_shift, + "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT ",context=\"%s\"", arg_uid_shift + TTY_GID, arg_selinux_apifs_context); else #endif (void) asprintf(&options, - "newinstance,ptmxmode=0666,mode=620,uid=" UID_FMT ",gid=" GID_FMT, - arg_uid_shift, + "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT, arg_uid_shift + TTY_GID); if (!options) @@ -2507,7 +2505,7 @@ static int reset_audit_loginuid(void) { if (streq(p, "4294967295")) return 0; - r = write_string_file("/proc/self/loginuid", "4294967295"); + r = write_string_file("/proc/self/loginuid", "4294967295", 0); if (r < 0) { log_error_errno(r, "Failed to reset audit login UID. This probably means that your kernel is too\n" @@ -4447,13 +4445,13 @@ static int setup_uid_map(pid_t pid) { xsprintf(uid_map, "/proc/" PID_FMT "/uid_map", pid); xsprintf(line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0, arg_uid_shift, arg_uid_range); - r = write_string_file(uid_map, line); + r = write_string_file(uid_map, line, 0); if (r < 0) return log_error_errno(r, "Failed to write UID map: %m"); /* We always assign the same UID and GID ranges */ xsprintf(uid_map, "/proc/" PID_FMT "/gid_map", pid); - r = write_string_file(uid_map, line); + r = write_string_file(uid_map, line, 0); if (r < 0) return log_error_errno(r, "Failed to write GID map: %m"); diff --git a/src/nss-mymachines/nss-mymachines.c b/src/nss-mymachines/nss-mymachines.c index f712033e6c..cdec83d074 100644 --- a/src/nss-mymachines/nss-mymachines.c +++ b/src/nss-mymachines/nss-mymachines.c @@ -28,9 +28,12 @@ #include "util.h" #include "nss-util.h" #include "bus-util.h" +#include "bus-common-errors.h" #include "in-addr-util.h" NSS_GETHOSTBYNAME_PROTOTYPES(mymachines); +NSS_GETPW_PROTOTYPES(mymachines); +NSS_GETGR_PROTOTYPES(mymachines); static int count_addresses(sd_bus_message *m, int af, unsigned *ret) { unsigned c = 0; @@ -380,4 +383,319 @@ fail: return NSS_STATUS_UNAVAIL; } -NSS_GETHOSTBYNAME_FALLBACKS(mymachines) +NSS_GETHOSTBYNAME_FALLBACKS(mymachines); + +enum nss_status _nss_mymachines_getpwnam_r( + const char *name, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message* reply = NULL; + _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; + const char *p, *e, *machine; + uint32_t mapped; + uid_t uid; + size_t l; + int r; + + assert(name); + assert(pwd); + + p = startswith(name, "vu-"); + if (!p) + goto not_found; + + e = strrchr(p, '-'); + if (!e || e == p) + goto not_found; + + r = parse_uid(e + 1, &uid); + if (r < 0) + goto not_found; + + machine = strndupa(p, e - p); + if (!machine_name_is_valid(machine)) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapFromMachineUser", + &error, + &reply, + "su", + machine, (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &mapped); + if (r < 0) + goto fail; + + l = strlen(name); + if (buflen < l+1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memcpy(buffer, name, l+1); + + pwd->pw_name = buffer; + pwd->pw_uid = mapped; + pwd->pw_gid = 65534; /* nobody */ + pwd->pw_gecos = buffer; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_getpwuid_r( + uid_t uid, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message* reply = NULL; + _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; + const char *machine, *object; + uint32_t mapped; + int r; + + if (UID_IS_INVALID(uid)) { + r = -EINVAL; + goto fail; + } + + /* We consider all uids < 65536 host uids */ + if (uid < 0x10000) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapToMachineUser", + &error, + &reply, + "u", + (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); + if (r < 0) + goto fail; + + if (snprintf(buffer, buflen, "vu-%s-" UID_FMT, machine, (uid_t) mapped) >= (int) buflen) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + pwd->pw_name = buffer; + pwd->pw_uid = uid; + pwd->pw_gid = 65534; /* nobody */ + pwd->pw_gecos = buffer; + pwd->pw_passwd = (char*) "*"; /* locked */ + pwd->pw_dir = (char*) "/"; + pwd->pw_shell = (char*) "/sbin/nologin"; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_getgrnam_r( + const char *name, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message* reply = NULL; + _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; + const char *p, *e, *machine; + uint32_t mapped; + uid_t gid; + size_t l; + int r; + + assert(name); + assert(gr); + + p = startswith(name, "vg-"); + if (!p) + goto not_found; + + e = strrchr(p, '-'); + if (!e || e == p) + goto not_found; + + r = parse_gid(e + 1, &gid); + if (r < 0) + goto not_found; + + machine = strndupa(p, e - p); + if (!machine_name_is_valid(machine)) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapFromMachineGroup", + &error, + &reply, + "su", + machine, (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &mapped); + if (r < 0) + goto fail; + + l = sizeof(char*) + strlen(name) + 1; + if (buflen < l) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + strcpy(buffer + sizeof(char*), name); + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = gid; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_getgrgid_r( + gid_t gid, + struct group *gr, + char *buffer, size_t buflen, + int *errnop) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message* reply = NULL; + _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; + const char *machine, *object; + uint32_t mapped; + int r; + + if (GID_IS_INVALID(gid)) { + r = -EINVAL; + goto fail; + } + + /* We consider all gids < 65536 host gids */ + if (gid < 0x10000) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapToMachineGroup", + &error, + &reply, + "u", + (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); + if (r < 0) + goto fail; + + if (buflen < sizeof(char*) + 1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + if (snprintf(buffer + sizeof(char*), buflen - sizeof(char*), "vg-%s-" GID_FMT, machine, (gid_t) mapped) >= (int) buflen) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + gr->gr_name = buffer + sizeof(char*); + gr->gr_gid = gid; + gr->gr_passwd = (char*) "*"; /* locked */ + gr->gr_mem = (char**) buffer; + + *errnop = 0; + return NSS_STATUS_SUCCESS; + +not_found: + *errnop = 0; + return NSS_STATUS_NOTFOUND; + +fail: + *errnop = -r; + return NSS_STATUS_UNAVAIL; +} diff --git a/src/nss-mymachines/nss-mymachines.sym b/src/nss-mymachines/nss-mymachines.sym index f80b51c1aa..0728ac3ba7 100644 --- a/src/nss-mymachines/nss-mymachines.sym +++ b/src/nss-mymachines/nss-mymachines.sym @@ -13,5 +13,9 @@ global: _nss_mymachines_gethostbyname2_r; _nss_mymachines_gethostbyname3_r; _nss_mymachines_gethostbyname4_r; + _nss_mymachines_getpwnam_r; + _nss_mymachines_getpwuid_r; + _nss_mymachines_getgrnam_r; + _nss_mymachines_getgrgid_r; local: *; }; diff --git a/src/python-systemd/.gitignore b/src/python-systemd/.gitignore deleted file mode 100644 index 4124b7affd..0000000000 --- a/src/python-systemd/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/id128-constants.h -*.py[oc] diff --git a/src/python-systemd/Makefile b/src/python-systemd/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/python-systemd/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile
\ No newline at end of file diff --git a/src/python-systemd/__init__.py b/src/python-systemd/__init__.py deleted file mode 100644 index 0d56b992f4..0000000000 --- a/src/python-systemd/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2012 David Strauss -# -# systemd 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. -# -# systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. diff --git a/src/python-systemd/_daemon.c b/src/python-systemd/_daemon.c deleted file mode 100644 index 7c5f1b2bb6..0000000000 --- a/src/python-systemd/_daemon.c +++ /dev/null @@ -1,331 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#define PY_SSIZE_T_CLEAN -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wredundant-decls" -#include <Python.h> -#pragma GCC diagnostic pop - -#include <stdbool.h> -#include <assert.h> -#include <sys/socket.h> - -#include "systemd/sd-daemon.h" -#include "pyutil.h" -#include "macro.h" - -PyDoc_STRVAR(module__doc__, - "Python interface to the libsystemd-daemon library.\n\n" - "Provides _listen_fds, notify, booted, and is_* functions\n" - "which wrap sd_listen_fds, sd_notify, sd_booted, sd_is_* and\n" - "useful for socket activation and checking if the system is\n" - "running under systemd." -); - -PyDoc_STRVAR(booted__doc__, - "booted() -> bool\n\n" - "Return True iff this system is running under systemd.\n" - "Wraps sd_daemon_booted(3)." -); - -static PyObject* booted(PyObject *self, PyObject *args) { - int r; - assert(args == NULL); - - r = sd_booted(); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - -PyDoc_STRVAR(notify__doc__, - "notify(status, unset_environment=False) -> bool\n\n" - "Send a message to the init system about a status change.\n" - "Wraps sd_notify(3)."); - -static PyObject* notify(PyObject *self, PyObject *args, PyObject *keywds) { - int r; - const char* msg; - int unset = false; - - static const char* const kwlist[] = { - "status", - "unset_environment", - NULL, - }; -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 - if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|p:notify", - (char**) kwlist, &msg, &unset)) - return NULL; -#else - PyObject *obj = NULL; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|O:notify", - (char**) kwlist, &msg, &obj)) - return NULL; - if (obj != NULL) - unset = PyObject_IsTrue(obj); - if (unset < 0) - return NULL; -#endif - - r = sd_notify(unset, msg); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - - -PyDoc_STRVAR(listen_fds__doc__, - "_listen_fds(unset_environment=True) -> int\n\n" - "Return the number of descriptors passed to this process by the init system\n" - "as part of the socket-based activation logic.\n" - "Wraps sd_listen_fds(3)." -); - -static PyObject* listen_fds(PyObject *self, PyObject *args, PyObject *keywds) { - int r; - int unset = true; - - static const char* const kwlist[] = {"unset_environment", NULL}; -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|p:_listen_fds", - (char**) kwlist, &unset)) - return NULL; -#else - PyObject *obj = NULL; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|O:_listen_fds", - (char**) kwlist, &obj)) - return NULL; - if (obj != NULL) - unset = PyObject_IsTrue(obj); - if (unset < 0) - return NULL; -#endif - - r = sd_listen_fds(unset); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return long_FromLong(r); -} - -PyDoc_STRVAR(is_fifo__doc__, - "_is_fifo(fd, path) -> bool\n\n" - "Returns True iff the descriptor refers to a FIFO or a pipe.\n" - "Wraps sd_is_fifo(3)." -); - - -static PyObject* is_fifo(PyObject *self, PyObject *args) { - int r; - int fd; - const char *path = NULL; - -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 - if (!PyArg_ParseTuple(args, "i|O&:_is_fifo", - &fd, Unicode_FSConverter, &path)) - return NULL; -#else - if (!PyArg_ParseTuple(args, "i|z:_is_fifo", &fd, &path)) - return NULL; -#endif - - r = sd_is_fifo(fd, path); - if (set_error(r, path, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - - -PyDoc_STRVAR(is_mq__doc__, - "_is_mq(fd, path) -> bool\n\n" - "Returns True iff the descriptor refers to a POSIX message queue.\n" - "Wraps sd_is_mq(3)." -); - -static PyObject* is_mq(PyObject *self, PyObject *args) { - int r; - int fd; - const char *path = NULL; - -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 - if (!PyArg_ParseTuple(args, "i|O&:_is_mq", - &fd, Unicode_FSConverter, &path)) - return NULL; -#else - if (!PyArg_ParseTuple(args, "i|z:_is_mq", &fd, &path)) - return NULL; -#endif - - r = sd_is_mq(fd, path); - if (set_error(r, path, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - - - -PyDoc_STRVAR(is_socket__doc__, - "_is_socket(fd, family=AF_UNSPEC, type=0, listening=-1) -> bool\n\n" - "Returns True iff the descriptor refers to a socket.\n" - "Wraps sd_is_socket(3).\n\n" - "Constants for `family` are defined in the socket module." -); - -static PyObject* is_socket(PyObject *self, PyObject *args) { - int r; - int fd, family = AF_UNSPEC, type = 0, listening = -1; - - if (!PyArg_ParseTuple(args, "i|iii:_is_socket", - &fd, &family, &type, &listening)) - return NULL; - - r = sd_is_socket(fd, family, type, listening); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - - -PyDoc_STRVAR(is_socket_inet__doc__, - "_is_socket_inet(fd, family=AF_UNSPEC, type=0, listening=-1, port=0) -> bool\n\n" - "Wraps sd_is_socket_inet(3).\n\n" - "Constants for `family` are defined in the socket module." -); - -static PyObject* is_socket_inet(PyObject *self, PyObject *args) { - int r; - int fd, family = AF_UNSPEC, type = 0, listening = -1, port = 0; - - if (!PyArg_ParseTuple(args, "i|iiii:_is_socket_inet", - &fd, &family, &type, &listening, &port)) - return NULL; - - if (port < 0 || port > UINT16_MAX) { - set_error(-EINVAL, NULL, "port must fit into uint16_t"); - return NULL; - } - - r = sd_is_socket_inet(fd, family, type, listening, (uint16_t) port); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - - -PyDoc_STRVAR(is_socket_unix__doc__, - "_is_socket_unix(fd, type, listening, path) -> bool\n\n" - "Wraps sd_is_socket_unix(3)." -); - -static PyObject* is_socket_unix(PyObject *self, PyObject *args) { - int r; - int fd, type = 0, listening = -1; - char* path = NULL; - Py_ssize_t length = 0; - -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 - _cleanup_Py_DECREF_ PyObject *_path = NULL; - if (!PyArg_ParseTuple(args, "i|iiO&:_is_socket_unix", - &fd, &type, &listening, Unicode_FSConverter, &_path)) - return NULL; - if (_path) { - assert(PyBytes_Check(_path)); - if (PyBytes_AsStringAndSize(_path, &path, &length)) - return NULL; - } -#else - if (!PyArg_ParseTuple(args, "i|iiz#:_is_socket_unix", - &fd, &type, &listening, &path, &length)) - return NULL; -#endif - - r = sd_is_socket_unix(fd, type, listening, path, length); - if (set_error(r, path, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - - -static PyMethodDef methods[] = { - { "booted", booted, METH_NOARGS, booted__doc__}, - { "notify", (PyCFunction) notify, METH_VARARGS | METH_KEYWORDS, notify__doc__}, - { "_listen_fds", (PyCFunction) listen_fds, METH_VARARGS | METH_KEYWORDS, listen_fds__doc__}, - { "_is_fifo", is_fifo, METH_VARARGS, is_fifo__doc__}, - { "_is_mq", is_mq, METH_VARARGS, is_mq__doc__}, - { "_is_socket", is_socket, METH_VARARGS, is_socket__doc__}, - { "_is_socket_inet", is_socket_inet, METH_VARARGS, is_socket_inet__doc__}, - { "_is_socket_unix", is_socket_unix, METH_VARARGS, is_socket_unix__doc__}, - { NULL, NULL, 0, NULL } /* Sentinel */ -}; - -#if PY_MAJOR_VERSION < 3 - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC init_daemon(void) { - PyObject *m; - - m = Py_InitModule3("_daemon", methods, module__doc__); - if (m == NULL) - return; - - PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START); - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); -} -REENABLE_WARNING; - -#else - -static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "_daemon", /* name of module */ - module__doc__, /* module documentation, may be NULL */ - 0, /* size of per-interpreter state of the module */ - methods -}; - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC PyInit__daemon(void) { - PyObject *m; - - m = PyModule_Create(&module); - if (m == NULL) - return NULL; - - if (PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START) || - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { - Py_DECREF(m); - return NULL; - } - - return m; -} -REENABLE_WARNING; - -#endif diff --git a/src/python-systemd/_journal.c b/src/python-systemd/_journal.c deleted file mode 100644 index 456e4a2796..0000000000 --- a/src/python-systemd/_journal.c +++ /dev/null @@ -1,157 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2012 David Strauss <david@davidstrauss.net> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <Python.h> - -#include <alloca.h> -#include "util.h" - -#define SD_JOURNAL_SUPPRESS_LOCATION -#include "systemd/sd-journal.h" - -PyDoc_STRVAR(journal_sendv__doc__, - "sendv('FIELD=value', 'FIELD=value', ...) -> None\n\n" - "Send an entry to the journal." -); - -static PyObject *journal_sendv(PyObject *self, PyObject *args) { - struct iovec *iov = NULL; - int argc; - int i, r; - PyObject *ret = NULL; - PyObject **encoded; - - /* Allocate an array for the argument strings */ - argc = PyTuple_Size(args); - encoded = alloca0(argc * sizeof(PyObject*)); - - /* Allocate sufficient iovector space for the arguments. */ - iov = alloca(argc * sizeof(struct iovec)); - - /* Iterate through the Python arguments and fill the iovector. */ - for (i = 0; i < argc; ++i) { - PyObject *item = PyTuple_GetItem(args, i); - char *stritem; - Py_ssize_t length; - - if (PyUnicode_Check(item)) { - encoded[i] = PyUnicode_AsEncodedString(item, "utf-8", "strict"); - if (encoded[i] == NULL) - goto out; - item = encoded[i]; - } - if (PyBytes_AsStringAndSize(item, &stritem, &length)) - goto out; - - iov[i].iov_base = stritem; - iov[i].iov_len = length; - } - - /* Send the iovector to the journal. */ - r = sd_journal_sendv(iov, argc); - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_IOError); - goto out; - } - - /* End with success. */ - Py_INCREF(Py_None); - ret = Py_None; - -out: - for (i = 0; i < argc; ++i) - Py_XDECREF(encoded[i]); - - return ret; -} - -PyDoc_STRVAR(journal_stream_fd__doc__, - "stream_fd(identifier, priority, level_prefix) -> fd\n\n" - "Open a stream to journal by calling sd_journal_stream_fd(3)." -); - -static PyObject* journal_stream_fd(PyObject *self, PyObject *args) { - const char* identifier; - int priority, level_prefix; - int fd; - - if (!PyArg_ParseTuple(args, "sii:stream_fd", - &identifier, &priority, &level_prefix)) - return NULL; - - fd = sd_journal_stream_fd(identifier, priority, level_prefix); - if (fd < 0) { - errno = -fd; - return PyErr_SetFromErrno(PyExc_IOError); - } - - return PyLong_FromLong(fd); -} - -static PyMethodDef methods[] = { - { "sendv", journal_sendv, METH_VARARGS, journal_sendv__doc__ }, - { "stream_fd", journal_stream_fd, METH_VARARGS, journal_stream_fd__doc__ }, - { NULL, NULL, 0, NULL } /* Sentinel */ -}; - -#if PY_MAJOR_VERSION < 3 - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC init_journal(void) { - PyObject *m; - - m = Py_InitModule("_journal", methods); - if (m == NULL) - return; - - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); -} -REENABLE_WARNING; - -#else - -static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "_journal", /* name of module */ - NULL, /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module */ - methods -}; - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC PyInit__journal(void) { - PyObject *m; - - m = PyModule_Create(&module); - if (m == NULL) - return NULL; - - if (PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { - Py_DECREF(m); - return NULL; - } - - return m; -} -REENABLE_WARNING; - -#endif diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c deleted file mode 100644 index 3a561269a7..0000000000 --- a/src/python-systemd/_reader.c +++ /dev/null @@ -1,1106 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Steven Hiscocks, Zbigniew JÄ™drzejewski-Szmek - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <Python.h> -#include <structmember.h> -#include <datetime.h> -#include <time.h> -#include <stdio.h> - -#include "systemd/sd-journal.h" - -#include "pyutil.h" -#include "macro.h" -#include "util.h" -#include "strv.h" -#include "build.h" - -typedef struct { - PyObject_HEAD - sd_journal *j; -} Reader; -static PyTypeObject ReaderType; - -PyDoc_STRVAR(module__doc__, - "Class to reads the systemd journal similar to journalctl."); - - -#if PY_MAJOR_VERSION >= 3 -static PyTypeObject MonotonicType; - -PyDoc_STRVAR(MonotonicType__doc__, - "A tuple of (timestamp, bootid) for holding monotonic timestamps"); - -static PyStructSequence_Field MonotonicType_fields[] = { - {(char*) "timestamp", (char*) "Time"}, - {(char*) "bootid", (char*) "Unique identifier of the boot"}, - {} /* Sentinel */ -}; - -static PyStructSequence_Desc Monotonic_desc = { - (char*) "journal.Monotonic", - MonotonicType__doc__, - MonotonicType_fields, - 2, -}; -#endif - -/** - * Convert a Python sequence object into a strv (char**), and - * None into a NULL pointer. - */ -static int strv_converter(PyObject* obj, void *_result) { - char ***result = _result; - Py_ssize_t i, len; - - assert(result); - - if (!obj) - return 0; - - if (obj == Py_None) { - *result = NULL; - return 1; - } - - if (!PySequence_Check(obj)) - return 0; - - len = PySequence_Length(obj); - *result = new0(char*, len + 1); - if (!*result) { - set_error(-ENOMEM, NULL, NULL); - return 0; - } - - for (i = 0; i < len; i++) { - PyObject *item; -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 - int r; - PyObject *bytes; -#endif - char *s, *s2; - - item = PySequence_ITEM(obj, i); -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 - r = PyUnicode_FSConverter(item, &bytes); - if (r == 0) - goto cleanup; - - s = PyBytes_AsString(bytes); -#else - s = PyString_AsString(item); -#endif - if (!s) - goto cleanup; - - s2 = strdup(s); - if (!s2) - log_oom(); - - (*result)[i] = s2; - } - - return 1; - -cleanup: - strv_free(*result); - *result = NULL; - - return 0; -} - -static void Reader_dealloc(Reader* self) { - sd_journal_close(self->j); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -PyDoc_STRVAR(Reader__doc__, - "_Reader([flags | path | files]) -> ...\n\n" - "_Reader allows filtering and retrieval of Journal entries.\n" - "Note: this is a low-level interface, and probably not what you\n" - "want, use systemd.journal.Reader instead.\n\n" - "Argument `flags` sets open flags of the journal, which can be one\n" - "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n" - "journal on local machine only; RUNTIME_ONLY opens only\n" - "volatile journal files; and SYSTEM opens journal files of\n" - "system services and the kernel, and CURRENT_USER opens files\n" - "of the current user.\n\n" - "Argument `path` is the directory of journal files.\n" - "Argument `files` is a list of files. Note that\n" - "`flags`, `path`, and `files` are exclusive.\n\n" - "_Reader implements the context manager protocol: the journal\n" - "will be closed when exiting the block."); -static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) { - int flags = 0, r; - char *path = NULL; - char **files = NULL; - - static const char* const kwlist[] = {"flags", "path", "files", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izO&:__init__", (char**) kwlist, - &flags, &path, strv_converter, &files)) - return -1; - - if (!!flags + !!path + !!files > 1) { - PyErr_SetString(PyExc_ValueError, "cannot use more than one of flags, path, and files"); - return -1; - } - - if (!flags) - flags = SD_JOURNAL_LOCAL_ONLY; - - Py_BEGIN_ALLOW_THREADS - if (path) - r = sd_journal_open_directory(&self->j, path, 0); - else if (files) - r = sd_journal_open_files(&self->j, (const char**) files, 0); - else - r = sd_journal_open(&self->j, flags); - Py_END_ALLOW_THREADS - - return set_error(r, path, "Invalid flags or path"); -} - -PyDoc_STRVAR(Reader_fileno__doc__, - "fileno() -> int\n\n" - "Get a file descriptor to poll for changes in the journal.\n" - "This method invokes sd_journal_get_fd().\n" - "See man:sd_journal_get_fd(3)."); -static PyObject* Reader_fileno(Reader *self, PyObject *args) { - int fd; - - fd = sd_journal_get_fd(self->j); - set_error(fd, NULL, NULL); - if (fd < 0) - return NULL; - return long_FromLong(fd); -} - -PyDoc_STRVAR(Reader_reliable_fd__doc__, - "reliable_fd() -> bool\n\n" - "Returns True iff the journal can be polled reliably.\n" - "This method invokes sd_journal_reliable_fd().\n" - "See man:sd_journal_reliable_fd(3)."); -static PyObject* Reader_reliable_fd(Reader *self, PyObject *args) { - int r; - - r = sd_journal_reliable_fd(self->j); - if (set_error(r, NULL, NULL) < 0) - return NULL; - return PyBool_FromLong(r); -} - -PyDoc_STRVAR(Reader_get_events__doc__, - "get_events() -> int\n\n" - "Returns a mask of poll() events to wait for on the file\n" - "descriptor returned by .fileno().\n\n" - "See man:sd_journal_get_events(3) for further discussion."); -static PyObject* Reader_get_events(Reader *self, PyObject *args) { - int r; - - r = sd_journal_get_events(self->j); - if (set_error(r, NULL, NULL) < 0) - return NULL; - return long_FromLong(r); -} - -PyDoc_STRVAR(Reader_get_timeout__doc__, - "get_timeout() -> int or None\n\n" - "Returns a timeout value for usage in poll(), the time since the\n" - "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" - "is necessary.\n\n" - "The return value must be converted to a relative timeout in\n" - "milliseconds if it is to be used as an argument for poll().\n" - "See man:sd_journal_get_timeout(3) for further discussion."); -static PyObject* Reader_get_timeout(Reader *self, PyObject *args) { - int r; - uint64_t t; - - r = sd_journal_get_timeout(self->j, &t); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - if (t == (uint64_t) -1) - Py_RETURN_NONE; - - assert_cc(sizeof(unsigned long long) == sizeof(t)); - return PyLong_FromUnsignedLongLong(t); -} - -PyDoc_STRVAR(Reader_get_timeout_ms__doc__, - "get_timeout_ms() -> int\n\n" - "Returns a timeout value suitable for usage in poll(), the value\n" - "returned by .get_timeout() converted to relative ms, or -1 if\n" - "no timeout is necessary."); -static PyObject* Reader_get_timeout_ms(Reader *self, PyObject *args) { - int r; - uint64_t t; - - r = sd_journal_get_timeout(self->j, &t); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return absolute_timeout(t); -} - -PyDoc_STRVAR(Reader_close__doc__, - "close() -> None\n\n" - "Free resources allocated by this Reader object.\n" - "This method invokes sd_journal_close().\n" - "See man:sd_journal_close(3)."); -static PyObject* Reader_close(Reader *self, PyObject *args) { - assert(self); - assert(!args); - - sd_journal_close(self->j); - self->j = NULL; - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_get_usage__doc__, - "get_usage() -> int\n\n" - "Returns the total disk space currently used by journal\n" - "files (in bytes). If `SD_JOURNAL_LOCAL_ONLY` was\n" - "passed when opening the journal this value will only reflect\n" - "the size of journal files of the local host, otherwise\n" - "of all hosts.\n\n" - "This method invokes sd_journal_get_usage().\n" - "See man:sd_journal_get_usage(3)."); -static PyObject* Reader_get_usage(Reader *self, PyObject *args) { - int r; - uint64_t bytes; - - r = sd_journal_get_usage(self->j, &bytes); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - assert_cc(sizeof(unsigned long long) == sizeof(bytes)); - return PyLong_FromUnsignedLongLong(bytes); -} - -PyDoc_STRVAR(Reader___enter____doc__, - "__enter__() -> self\n\n" - "Part of the context manager protocol.\n" - "Returns self.\n"); -static PyObject* Reader___enter__(PyObject *self, PyObject *args) { - assert(self); - assert(!args); - - Py_INCREF(self); - return self; -} - -PyDoc_STRVAR(Reader___exit____doc__, - "__exit__(type, value, traceback) -> None\n\n" - "Part of the context manager protocol.\n" - "Closes the journal.\n"); -static PyObject* Reader___exit__(Reader *self, PyObject *args) { - return Reader_close(self, NULL); -} - -PyDoc_STRVAR(Reader_next__doc__, - "next([skip]) -> bool\n\n" - "Go to the next log entry. Optional skip value means to go to\n" - "the `skip`\\-th log entry.\n" - "Returns False if at end of file, True otherwise."); -static PyObject* Reader_next(Reader *self, PyObject *args) { - int64_t skip = 1LL; - int r; - - if (!PyArg_ParseTuple(args, "|L:next", &skip)) - return NULL; - - if (skip == 0LL) { - PyErr_SetString(PyExc_ValueError, "skip must be nonzero"); - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - if (skip == 1LL) - r = sd_journal_next(self->j); - else if (skip == -1LL) - r = sd_journal_previous(self->j); - else if (skip > 1LL) - r = sd_journal_next_skip(self->j, skip); - else if (skip < -1LL) - r = sd_journal_previous_skip(self->j, -skip); - else - assert_not_reached("should not be here"); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - return PyBool_FromLong(r); -} - -PyDoc_STRVAR(Reader_previous__doc__, - "previous([skip]) -> bool\n\n" - "Go to the previous log entry. Optional skip value means to \n" - "go to the `skip`\\-th previous log entry.\n" - "Returns False if at start of file, True otherwise."); -static PyObject* Reader_previous(Reader *self, PyObject *args) { - int64_t skip = 1LL; - if (!PyArg_ParseTuple(args, "|L:previous", &skip)) - return NULL; - - return PyObject_CallMethod((PyObject *)self, (char*) "_next", - (char*) "L", -skip); -} - -static int extract(const char* msg, size_t msg_len, - PyObject **key, PyObject **value) { - PyObject *k = NULL, *v; - const char *delim_ptr; - - delim_ptr = memchr(msg, '=', msg_len); - if (!delim_ptr) { - PyErr_SetString(PyExc_OSError, - "journal gave us a field without '='"); - return -1; - } - - if (key) { - k = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); - if (!k) - return -1; - } - - if (value) { - v = PyBytes_FromStringAndSize(delim_ptr + 1, - (const char*) msg + msg_len - (delim_ptr + 1)); - if (!v) { - Py_XDECREF(k); - return -1; - } - - *value = v; - } - - if (key) - *key = k; - - return 0; -} - -PyDoc_STRVAR(Reader_get__doc__, - "get(str) -> str\n\n" - "Return data associated with this key in current log entry.\n" - "Throws KeyError is the data is not available."); -static PyObject* Reader_get(Reader *self, PyObject *args) { - const char* field; - const void* msg; - size_t msg_len; - PyObject *value; - int r; - - assert(self); - assert(args); - - if (!PyArg_ParseTuple(args, "s:get", &field)) - return NULL; - - r = sd_journal_get_data(self->j, field, &msg, &msg_len); - if (r == -ENOENT) { - PyErr_SetString(PyExc_KeyError, field); - return NULL; - } - if (set_error(r, NULL, "field name is not valid") < 0) - return NULL; - - r = extract(msg, msg_len, NULL, &value); - if (r < 0) - return NULL; - return value; -} - -PyDoc_STRVAR(Reader_get_all__doc__, - "_get_all() -> dict\n\n" - "Return dictionary of the current log entry."); -static PyObject* Reader_get_all(Reader *self, PyObject *args) { - PyObject *dict; - const void *msg; - size_t msg_len; - int r; - - dict = PyDict_New(); - if (!dict) - return NULL; - - SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) { - _cleanup_Py_DECREF_ PyObject *key = NULL, *value = NULL; - - r = extract(msg, msg_len, &key, &value); - if (r < 0) - goto error; - - if (PyDict_Contains(dict, key)) { - PyObject *cur_value = PyDict_GetItem(dict, key); - - if (PyList_CheckExact(cur_value)) { - r = PyList_Append(cur_value, value); - if (r < 0) - goto error; - } else { - _cleanup_Py_DECREF_ PyObject *tmp_list = PyList_New(0); - if (!tmp_list) - goto error; - - r = PyList_Append(tmp_list, cur_value); - if (r < 0) - goto error; - - r = PyList_Append(tmp_list, value); - if (r < 0) - goto error; - - r = PyDict_SetItem(dict, key, tmp_list); - if (r < 0) - goto error; - } - } else { - r = PyDict_SetItem(dict, key, value); - if (r < 0) - goto error; - } - } - - return dict; - -error: - Py_DECREF(dict); - return NULL; -} - -PyDoc_STRVAR(Reader_get_realtime__doc__, - "get_realtime() -> int\n\n" - "Return the realtime timestamp for the current journal entry\n" - "in microseconds.\n\n" - "Wraps sd_journal_get_realtime_usec().\n" - "See man:sd_journal_get_realtime_usec(3)."); -static PyObject* Reader_get_realtime(Reader *self, PyObject *args) { - uint64_t timestamp; - int r; - - assert(self); - assert(!args); - - r = sd_journal_get_realtime_usec(self->j, ×tamp); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); - return PyLong_FromUnsignedLongLong(timestamp); -} - -PyDoc_STRVAR(Reader_get_monotonic__doc__, - "get_monotonic() -> (timestamp, bootid)\n\n" - "Return the monotonic timestamp for the current journal entry\n" - "as a tuple of time in microseconds and bootid.\n\n" - "Wraps sd_journal_get_monotonic_usec().\n" - "See man:sd_journal_get_monotonic_usec(3)."); -static PyObject* Reader_get_monotonic(Reader *self, PyObject *args) { - uint64_t timestamp; - sd_id128_t id; - PyObject *monotonic, *bootid, *tuple; - int r; - - assert(self); - assert(!args); - - r = sd_journal_get_monotonic_usec(self->j, ×tamp, &id); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); - monotonic = PyLong_FromUnsignedLongLong(timestamp); - bootid = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); -#if PY_MAJOR_VERSION >= 3 - tuple = PyStructSequence_New(&MonotonicType); -#else - tuple = PyTuple_New(2); -#endif - if (!monotonic || !bootid || !tuple) { - Py_XDECREF(monotonic); - Py_XDECREF(bootid); - Py_XDECREF(tuple); - return NULL; - } - -#if PY_MAJOR_VERSION >= 3 - PyStructSequence_SET_ITEM(tuple, 0, monotonic); - PyStructSequence_SET_ITEM(tuple, 1, bootid); -#else - PyTuple_SET_ITEM(tuple, 0, monotonic); - PyTuple_SET_ITEM(tuple, 1, bootid); -#endif - - return tuple; -} - -PyDoc_STRVAR(Reader_add_match__doc__, - "add_match(match) -> None\n\n" - "Add a match to filter journal log entries. All matches of different\n" - "fields are combined with logical AND, and matches of the same field\n" - "are automatically combined with logical OR.\n" - "Match is a string of the form \"FIELD=value\"."); -static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds) { - char *match; - int match_len, r; - if (!PyArg_ParseTuple(args, "s#:add_match", &match, &match_len)) - return NULL; - - r = sd_journal_add_match(self->j, match, match_len); - if (set_error(r, NULL, "Invalid match") < 0) - return NULL; - - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_add_disjunction__doc__, - "add_disjunction() -> None\n\n" - "Inserts a logical OR between matches added since previous\n" - "add_disjunction() or add_conjunction() and the next\n" - "add_disjunction() or add_conjunction().\n\n" - "See man:sd_journal_add_disjunction(3) for explanation."); -static PyObject* Reader_add_disjunction(Reader *self, PyObject *args) { - int r; - r = sd_journal_add_disjunction(self->j); - if (set_error(r, NULL, NULL) < 0) - return NULL; - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_add_conjunction__doc__, - "add_conjunction() -> None\n\n" - "Inserts a logical AND between matches added since previous\n" - "add_disjunction() or add_conjunction() and the next\n" - "add_disjunction() or add_conjunction().\n\n" - "See man:sd_journal_add_disjunction(3) for explanation."); -static PyObject* Reader_add_conjunction(Reader *self, PyObject *args) { - int r; - r = sd_journal_add_conjunction(self->j); - if (set_error(r, NULL, NULL) < 0) - return NULL; - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_flush_matches__doc__, - "flush_matches() -> None\n\n" - "Clear all current match filters."); -static PyObject* Reader_flush_matches(Reader *self, PyObject *args) { - sd_journal_flush_matches(self->j); - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_seek_head__doc__, - "seek_head() -> None\n\n" - "Jump to the beginning of the journal.\n" - "This method invokes sd_journal_seek_head().\n" - "See man:sd_journal_seek_head(3)."); -static PyObject* Reader_seek_head(Reader *self, PyObject *args) { - int r; - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_head(self->j); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_seek_tail__doc__, - "seek_tail() -> None\n\n" - "Jump to the end of the journal.\n" - "This method invokes sd_journal_seek_tail().\n" - "See man:sd_journal_seek_tail(3)."); -static PyObject* Reader_seek_tail(Reader *self, PyObject *args) { - int r; - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_tail(self->j); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_seek_realtime__doc__, - "seek_realtime(realtime) -> None\n\n" - "Seek to nearest matching journal entry to `realtime`. Argument\n" - "`realtime` in specified in seconds."); -static PyObject* Reader_seek_realtime(Reader *self, PyObject *args) { - uint64_t timestamp; - int r; - - if (!PyArg_ParseTuple(args, "K:seek_realtime", ×tamp)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_realtime_usec(self->j, timestamp); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_seek_monotonic__doc__, - "seek_monotonic(monotonic[, bootid]) -> None\n\n" - "Seek to nearest matching journal entry to `monotonic`. Argument\n" - "`monotonic` is an timestamp from boot in microseconds.\n" - "Argument `bootid` is a string representing which boot the\n" - "monotonic time is reference to. Defaults to current bootid."); -static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args) { - char *bootid = NULL; - uint64_t timestamp; - sd_id128_t id; - int r; - - if (!PyArg_ParseTuple(args, "K|z:seek_monotonic", ×tamp, &bootid)) - return NULL; - - if (bootid) { - r = sd_id128_from_string(bootid, &id); - if (set_error(r, NULL, "Invalid bootid") < 0) - return NULL; - } else { - Py_BEGIN_ALLOW_THREADS - r = sd_id128_get_boot(&id); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_monotonic_usec(self->j, id, timestamp); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Reader_process__doc__, - "process() -> state change (integer)\n\n" - "Process events and reset the readable state of the file\n" - "descriptor returned by .fileno().\n\n" - "Will return constants: NOP if no change; APPEND if new\n" - "entries have been added to the end of the journal; and\n" - "INVALIDATE if journal files have been added or removed.\n\n" - "See man:sd_journal_process(3) for further discussion."); -static PyObject* Reader_process(Reader *self, PyObject *args) { - int r; - - assert(!args); - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_process(self->j); - Py_END_ALLOW_THREADS - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return long_FromLong(r); -} - -PyDoc_STRVAR(Reader_wait__doc__, - "wait([timeout]) -> state change (integer)\n\n" - "Wait for a change in the journal. Argument `timeout` specifies\n" - "the maximum number of microseconds to wait before returning\n" - "regardless of wheter the journal has changed. If `timeout` is -1,\n" - "then block forever.\n\n" - "Will return constants: NOP if no change; APPEND if new\n" - "entries have been added to the end of the journal; and\n" - "INVALIDATE if journal files have been added or removed.\n\n" - "See man:sd_journal_wait(3) for further discussion."); -static PyObject* Reader_wait(Reader *self, PyObject *args) { - int r; - int64_t timeout; - - if (!PyArg_ParseTuple(args, "|L:wait", &timeout)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_wait(self->j, timeout); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return long_FromLong(r); -} - -PyDoc_STRVAR(Reader_seek_cursor__doc__, - "seek_cursor(cursor) -> None\n\n" - "Seek to journal entry by given unique reference `cursor`."); -static PyObject* Reader_seek_cursor(Reader *self, PyObject *args) { - const char *cursor; - int r; - - if (!PyArg_ParseTuple(args, "s:seek_cursor", &cursor)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_cursor(self->j, cursor); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, "Invalid cursor") < 0) - return NULL; - - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Reader_get_cursor__doc__, - "get_cursor() -> str\n\n" - "Return a cursor string for the current journal entry.\n\n" - "Wraps sd_journal_get_cursor(). See man:sd_journal_get_cursor(3)."); -static PyObject* Reader_get_cursor(Reader *self, PyObject *args) { - _cleanup_free_ char *cursor = NULL; - int r; - - assert(self); - assert(!args); - - r = sd_journal_get_cursor(self->j, &cursor); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return unicode_FromString(cursor); -} - -PyDoc_STRVAR(Reader_test_cursor__doc__, - "test_cursor(str) -> bool\n\n" - "Test whether the cursor string matches current journal entry.\n\n" - "Wraps sd_journal_test_cursor(). See man:sd_journal_test_cursor(3)."); -static PyObject* Reader_test_cursor(Reader *self, PyObject *args) { - const char *cursor; - int r; - - assert(self); - assert(args); - - if (!PyArg_ParseTuple(args, "s:test_cursor", &cursor)) - return NULL; - - r = sd_journal_test_cursor(self->j, cursor); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return PyBool_FromLong(r); -} - -PyDoc_STRVAR(Reader_query_unique__doc__, - "query_unique(field) -> a set of values\n\n" - "Return a set of unique values appearing in journal for the\n" - "given `field`. Note this does not respect any journal matches."); -static PyObject* Reader_query_unique(Reader *self, PyObject *args) { - char *query; - int r; - const void *uniq; - size_t uniq_len; - PyObject *value_set, *key, *value; - - if (!PyArg_ParseTuple(args, "s:query_unique", &query)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_query_unique(self->j, query); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, "Invalid field name") < 0) - return NULL; - - value_set = PySet_New(0); - key = unicode_FromString(query); - - SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) { - const char *delim_ptr; - - delim_ptr = memchr(uniq, '=', uniq_len); - value = PyBytes_FromStringAndSize( - delim_ptr + 1, - (const char*) uniq + uniq_len - (delim_ptr + 1)); - PySet_Add(value_set, value); - Py_DECREF(value); - } - - Py_DECREF(key); - return value_set; -} - -PyDoc_STRVAR(Reader_get_catalog__doc__, - "get_catalog() -> str\n\n" - "Retrieve a message catalog entry for the current journal entry.\n" - "Will throw IndexError if the entry has no MESSAGE_ID\n" - "and KeyError is the id is specified, but hasn't been found\n" - "in the catalog.\n\n" - "Wraps man:sd_journal_get_catalog(3)."); -static PyObject* Reader_get_catalog(Reader *self, PyObject *args) { - int r; - _cleanup_free_ char *msg = NULL; - - assert(self); - assert(!args); - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_get_catalog(self->j, &msg); - Py_END_ALLOW_THREADS - - if (r == -ENOENT) { - const void* mid; - size_t mid_len; - - r = sd_journal_get_data(self->j, "MESSAGE_ID", &mid, &mid_len); - if (r == 0) { - const size_t l = sizeof("MESSAGE_ID"); - assert(mid_len > l); - PyErr_Format(PyExc_KeyError, "%.*s", (int) (mid_len - l), - (const char*) mid + l); - } else if (r == -ENOENT) - PyErr_SetString(PyExc_IndexError, "no MESSAGE_ID field"); - else - set_error(r, NULL, NULL); - return NULL; - } - - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return unicode_FromString(msg); -} - -PyDoc_STRVAR(get_catalog__doc__, - "get_catalog(id128) -> str\n\n" - "Retrieve a message catalog entry for the given id.\n" - "Wraps man:sd_journal_get_catalog_for_message_id(3)."); -static PyObject* get_catalog(PyObject *self, PyObject *args) { - int r; - char *id_ = NULL; - sd_id128_t id; - _cleanup_free_ char *msg = NULL; - - assert(args); - - if (!PyArg_ParseTuple(args, "z:get_catalog", &id_)) - return NULL; - - r = sd_id128_from_string(id_, &id); - if (set_error(r, NULL, "Invalid id128") < 0) - return NULL; - - Py_BEGIN_ALLOW_THREADS - r = sd_journal_get_catalog_for_message_id(id, &msg); - Py_END_ALLOW_THREADS - - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return unicode_FromString(msg); -} - -PyDoc_STRVAR(data_threshold__doc__, - "Threshold for field size truncation in bytes.\n\n" - "Fields longer than this will be truncated to the threshold size.\n" - "Defaults to 64Kb."); - -static PyObject* Reader_get_data_threshold(Reader *self, void *closure) { - size_t cvalue; - int r; - - r = sd_journal_get_data_threshold(self->j, &cvalue); - if (set_error(r, NULL, NULL) < 0) - return NULL; - - return long_FromSize_t(cvalue); -} - -static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure) { - int r; - - if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold"); - return -1; - } - - if (!long_Check(value)){ - PyErr_SetString(PyExc_TypeError, "Data threshold must be an int"); - return -1; - } - - r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value)); - return set_error(r, NULL, NULL); -} - -PyDoc_STRVAR(closed__doc__, - "True iff journal is closed"); -static PyObject* Reader_get_closed(Reader *self, void *closure) { - return PyBool_FromLong(self->j == NULL); -} - -static PyGetSetDef Reader_getsetters[] = { - { (char*) "data_threshold", - (getter) Reader_get_data_threshold, - (setter) Reader_set_data_threshold, - (char*) data_threshold__doc__, - NULL }, - { (char*) "closed", - (getter) Reader_get_closed, - NULL, - (char*) closed__doc__, - NULL }, - {} /* Sentinel */ -}; - -static PyMethodDef Reader_methods[] = { - {"fileno", (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__}, - {"reliable_fd", (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__}, - {"get_events", (PyCFunction) Reader_get_events, METH_NOARGS, Reader_get_events__doc__}, - {"get_timeout", (PyCFunction) Reader_get_timeout, METH_NOARGS, Reader_get_timeout__doc__}, - {"get_timeout_ms", (PyCFunction) Reader_get_timeout_ms, METH_NOARGS, Reader_get_timeout_ms__doc__}, - {"close", (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__}, - {"get_usage", (PyCFunction) Reader_get_usage, METH_NOARGS, Reader_get_usage__doc__}, - {"__enter__", (PyCFunction) Reader___enter__, METH_NOARGS, Reader___enter____doc__}, - {"__exit__", (PyCFunction) Reader___exit__, METH_VARARGS, Reader___exit____doc__}, - {"_next", (PyCFunction) Reader_next, METH_VARARGS, Reader_next__doc__}, - {"_previous", (PyCFunction) Reader_previous, METH_VARARGS, Reader_previous__doc__}, - {"_get", (PyCFunction) Reader_get, METH_VARARGS, Reader_get__doc__}, - {"_get_all", (PyCFunction) Reader_get_all, METH_NOARGS, Reader_get_all__doc__}, - {"_get_realtime", (PyCFunction) Reader_get_realtime, METH_NOARGS, Reader_get_realtime__doc__}, - {"_get_monotonic", (PyCFunction) Reader_get_monotonic, METH_NOARGS, Reader_get_monotonic__doc__}, - {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__}, - {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__}, - {"add_conjunction", (PyCFunction) Reader_add_conjunction, METH_NOARGS, Reader_add_conjunction__doc__}, - {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__}, - {"seek_head", (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__}, - {"seek_tail", (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__}, - {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__}, - {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__}, - {"process", (PyCFunction) Reader_process, METH_NOARGS, Reader_process__doc__}, - {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__}, - {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__}, - {"_get_cursor", (PyCFunction) Reader_get_cursor, METH_NOARGS, Reader_get_cursor__doc__}, - {"test_cursor", (PyCFunction) Reader_test_cursor, METH_VARARGS, Reader_test_cursor__doc__}, - {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__}, - {"get_catalog", (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__}, - {} /* Sentinel */ -}; - -static PyTypeObject ReaderType = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "_reader._Reader", - .tp_basicsize = sizeof(Reader), - .tp_dealloc = (destructor) Reader_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_doc = Reader__doc__, - .tp_methods = Reader_methods, - .tp_getset = Reader_getsetters, - .tp_init = (initproc) Reader_init, - .tp_new = PyType_GenericNew, -}; - -static PyMethodDef methods[] = { - { "_get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__}, - {} /* Sentinel */ -}; - -#if PY_MAJOR_VERSION >= 3 -static PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "_reader", - module__doc__, - -1, - methods, -}; -#endif - -#if PY_MAJOR_VERSION >= 3 -static bool initialized = false; -#endif - -DISABLE_WARNING_MISSING_PROTOTYPES; - -PyMODINIT_FUNC -#if PY_MAJOR_VERSION >= 3 -PyInit__reader(void) -#else -init_reader(void) -#endif -{ - PyObject* m; - - PyDateTime_IMPORT; - - if (PyType_Ready(&ReaderType) < 0) -#if PY_MAJOR_VERSION >= 3 - return NULL; -#else - return; -#endif - -#if PY_MAJOR_VERSION >= 3 - m = PyModule_Create(&module); - if (m == NULL) - return NULL; - - if (!initialized) { - PyStructSequence_InitType(&MonotonicType, &Monotonic_desc); - initialized = true; - } -#else - m = Py_InitModule3("_reader", methods, module__doc__); - if (m == NULL) - return; -#endif - - Py_INCREF(&ReaderType); -#if PY_MAJOR_VERSION >= 3 - Py_INCREF(&MonotonicType); -#endif - if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) || -#if PY_MAJOR_VERSION >= 3 - PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) || -#endif - PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) || - PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) || - PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) || - PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) || - PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) || - PyModule_AddIntConstant(m, "SYSTEM", SD_JOURNAL_SYSTEM) || - PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY) || - PyModule_AddIntConstant(m, "CURRENT_USER", SD_JOURNAL_CURRENT_USER) || - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { -#if PY_MAJOR_VERSION >= 3 - Py_DECREF(m); - return NULL; -#endif - } - -#if PY_MAJOR_VERSION >= 3 - return m; -#endif -} - -REENABLE_WARNING; diff --git a/src/python-systemd/daemon.py b/src/python-systemd/daemon.py deleted file mode 100644 index 82011ca606..0000000000 --- a/src/python-systemd/daemon.py +++ /dev/null @@ -1,55 +0,0 @@ -from ._daemon import (__version__, - booted, - notify, - _listen_fds, - _is_fifo, - _is_socket, - _is_socket_inet, - _is_socket_unix, - _is_mq, - LISTEN_FDS_START) -from socket import AF_UNSPEC as _AF_UNSPEC - -def _convert_fileobj(fileobj): - try: - return fileobj.fileno() - except AttributeError: - return fileobj - -def is_fifo(fileobj, path=None): - fd = _convert_fileobj(fileobj) - return _is_fifo(fd, path) - -def is_socket(fileobj, family=_AF_UNSPEC, type=0, listening=-1): - fd = _convert_fileobj(fileobj) - return _is_socket(fd, family, type, listening) - -def is_socket_inet(fileobj, family=_AF_UNSPEC, type=0, listening=-1, port=0): - fd = _convert_fileobj(fileobj) - return _is_socket_inet(fd, family, type, listening, port) - -def is_socket_unix(fileobj, type=0, listening=-1, path=None): - fd = _convert_fileobj(fileobj) - return _is_socket_unix(fd, type, listening, path) - -def is_mq(fileobj, path=None): - fd = _convert_fileobj(fileobj) - return _is_mq(fd, path) - -def listen_fds(unset_environment=True): - """Return a list of socket activated descriptors - - Example:: - - (in primary window) - $ systemd-activate -l 2000 python3 -c \\ - 'from systemd.daemon import listen_fds; print(listen_fds())' - (in another window) - $ telnet localhost 2000 - (in primary window) - ... - Execing python3 (...) - [3] - """ - num = _listen_fds(unset_environment) - return list(range(LISTEN_FDS_START, LISTEN_FDS_START + num)) diff --git a/src/python-systemd/docs/.gitignore b/src/python-systemd/docs/.gitignore deleted file mode 100644 index b06a965e6a..0000000000 --- a/src/python-systemd/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!layout.html diff --git a/src/python-systemd/docs/conf.py b/src/python-systemd/docs/conf.py deleted file mode 100644 index 1919170bb1..0000000000 --- a/src/python-systemd/docs/conf.py +++ /dev/null @@ -1,279 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-systemd documentation build configuration file, created by -# sphinx-quickstart on Sat Feb 9 13:49:42 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['.'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'python-systemd' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# "<project> v<release> documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['.'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -html_show_sourcelink = False - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a <link> tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-systemddoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'python-systemd.tex', u'python-systemd Documentation', - None, 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'python-systemd', u'python-systemd Documentation', - [], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'python-systemd', u'python-systemd Documentation', - u'David Strauss, Zbigniew JÄ™drzejewski-Szmek, Marti Raudsepp, Steven Hiscocks', 'python-systemd', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = u'python-systemd' -epub_author = u'David Strauss, Zbigniew JÄ™drzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' -epub_publisher = u'David Strauss, Zbigniew JÄ™drzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' -epub_copyright = u'2013, David Strauss, Zbigniew JÄ™drzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' - -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# A tuple containing the cover image and cover page html template filenames. -#epub_cover = () - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 - -# Allow duplicate toc entries. -#epub_tocdup = True - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/src/python-systemd/docs/daemon.rst b/src/python-systemd/docs/daemon.rst deleted file mode 100644 index 0ad11edaf3..0000000000 --- a/src/python-systemd/docs/daemon.rst +++ /dev/null @@ -1,18 +0,0 @@ -`systemd.daemon` module -======================= - -.. automodule:: systemd.daemon - :members: - :undoc-members: - :inherited-members: - - .. autoattribute:: systemd.daemon.LISTEN_FDS_START - - .. autofunction:: _listen_fds - .. autofunction:: _is_fifo - .. autofunction:: _is_socket - .. autofunction:: _is_socket_unix - .. autofunction:: _is_socket_inet - .. autofunction:: _is_mq - .. autofunction:: notify - .. autofunction:: booted diff --git a/src/python-systemd/docs/default.css b/src/python-systemd/docs/default.css deleted file mode 100644 index 7c097d64a2..0000000000 --- a/src/python-systemd/docs/default.css +++ /dev/null @@ -1,196 +0,0 @@ -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { - background-color: #dddddd; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; -} - -div.sphinxsidebar input { - border: 1px solid #000000; - font-family: sans-serif; - font-size: 1em; -} - - - -/* -- hyperlink styles ------------------------------------------------------ */ - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - - - -/* -- body styles ----------------------------------------------------------- */ - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - text-align: justify; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #333333; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -th { - background-color: #ede; -} - -.warning tt { - background: #efc2c2; -} - -.note tt { - background: #d6d6d6; -} - -.viewcode-back { - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} diff --git a/src/python-systemd/docs/id128.rst b/src/python-systemd/docs/id128.rst deleted file mode 100644 index 89c37f3470..0000000000 --- a/src/python-systemd/docs/id128.rst +++ /dev/null @@ -1,40 +0,0 @@ -`systemd.id128` module -====================== - -.. automodule:: systemd.id128 - :members: - :undoc-members: - :inherited-members: - - .. autoattribute:: systemd.id128.SD_MESSAGE_COREDUMP - .. autoattribute:: systemd.id128.SD_MESSAGE_FORWARD_SYSLOG_MISSED - .. autoattribute:: systemd.id128.SD_MESSAGE_HIBERNATE_KEY - .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_DROPPED - .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_MISSED - .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_START - .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_STOP - .. autoattribute:: systemd.id128.SD_MESSAGE_LID_CLOSED - .. autoattribute:: systemd.id128.SD_MESSAGE_LID_OPENED - .. autoattribute:: systemd.id128.SD_MESSAGE_OVERMOUNTING - .. autoattribute:: systemd.id128.SD_MESSAGE_POWER_KEY - .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_START - .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_STOP - .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_START - .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_STOP - .. autoattribute:: systemd.id128.SD_MESSAGE_SHUTDOWN - .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_START - .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_STOP - .. autoattribute:: systemd.id128.SD_MESSAGE_SPAWN_FAILED - .. autoattribute:: systemd.id128.SD_MESSAGE_STARTUP_FINISHED - .. autoattribute:: systemd.id128.SD_MESSAGE_SUSPEND_KEY - .. autoattribute:: systemd.id128.SD_MESSAGE_TIMEZONE_CHANGE - .. autoattribute:: systemd.id128.SD_MESSAGE_TIME_CHANGE - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_FAILED - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADED - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADING - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTED - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTING - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPED - .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPING - .. autoattribute:: systemd.id128.SD_MESSAGE_CONFIG_ERROR - .. autoattribute:: systemd.id128.SD_MESSAGE_BOOTCHART diff --git a/src/python-systemd/docs/index.rst b/src/python-systemd/docs/index.rst deleted file mode 100644 index e78d966274..0000000000 --- a/src/python-systemd/docs/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. python-systemd documentation master file, created by - sphinx-quickstart on Sat Feb 9 13:49:42 2013. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to python-systemd's documentation! -========================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - journal - id128 - daemon - login - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst deleted file mode 100644 index ea74cf85c4..0000000000 --- a/src/python-systemd/docs/journal.rst +++ /dev/null @@ -1,64 +0,0 @@ -`systemd.journal` module -======================== - -.. automodule:: systemd.journal - :members: send, sendv, stream, stream_fd - :undoc-members: - -`JournalHandler` class ----------------------- - -.. autoclass:: JournalHandler - -Accessing the Journal ---------------------- - -.. autoclass:: _Reader - :undoc-members: - :inherited-members: - -.. autoclass:: Reader - :undoc-members: - :inherited-members: - - .. automethod:: __init__ - -.. autofunction:: _get_catalog -.. autofunction:: get_catalog - -.. autoclass:: Monotonic - -.. autoattribute:: systemd.journal.DEFAULT_CONVERTERS - -Example: polling for journal events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This example shows that journal events can be waited for (using -e.g. `poll`). This makes it easy to integrate Reader in an external -event loop: - - >>> import select - >>> from systemd import journal - >>> j = journal.Reader() - >>> j.seek_tail() - >>> p = select.poll() - >>> p.register(j, j.get_events()) - >>> p.poll() - [(3, 1)] - >>> j.get_next() - - -Journal access types -~~~~~~~~~~~~~~~~~~~~ - -.. autoattribute:: systemd.journal.LOCAL_ONLY -.. autoattribute:: systemd.journal.RUNTIME_ONLY -.. autoattribute:: systemd.journal.SYSTEM -.. autoattribute:: systemd.journal.CURRENT_USER - -Journal event types -~~~~~~~~~~~~~~~~~~~ - -.. autoattribute:: systemd.journal.NOP -.. autoattribute:: systemd.journal.APPEND -.. autoattribute:: systemd.journal.INVALIDATE diff --git a/src/python-systemd/docs/layout.html b/src/python-systemd/docs/layout.html deleted file mode 100644 index 930a6a7afe..0000000000 --- a/src/python-systemd/docs/layout.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "!layout.html" %} - -{% block relbar1 %} - <a href="../man/systemd.index.html">Index </a>· - <a href="../man/systemd.directives.html">Directives </a>· - <a href="index.html">Python </a>· - <span style="float:right">systemd {{release}}</span> - <hr /> -{% endblock %} - -{# remove the lower relbar #} -{% block relbar2 %} {% endblock %} - -{# remove the footer #} -{% block footer %} {% endblock %} diff --git a/src/python-systemd/docs/login.rst b/src/python-systemd/docs/login.rst deleted file mode 100644 index 6b4de64c55..0000000000 --- a/src/python-systemd/docs/login.rst +++ /dev/null @@ -1,28 +0,0 @@ -`systemd.login` module -======================= - -.. automodule:: systemd.login - :members: - -.. autoclass:: Monitor - :undoc-members: - :inherited-members: - -Example: polling for events -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This example shows that session/uid/seat/machine events can be waited -for (using e.g. `poll`). This makes it easy to integrate Monitor in an -external event loop: - - >>> import select - >>> from systemd import login - >>> m = login.Monitor("machine") - >>> p = select.poll() - >>> p.register(m, m.get_events()) - >>> login.machine_names() - [] - >>> p.poll() - [(3, 1)] - >>> login.machine_names() - ['fedora-19.nspawn'] diff --git a/src/python-systemd/id128.c b/src/python-systemd/id128.c deleted file mode 100644 index 5ec7309a54..0000000000 --- a/src/python-systemd/id128.c +++ /dev/null @@ -1,163 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <Python.h> - -#include "systemd/sd-messages.h" - -#include "pyutil.h" -#include "log.h" -#include "util.h" -#include "macro.h" - -PyDoc_STRVAR(module__doc__, - "Python interface to the libsystemd-id128 library.\n\n" - "Provides SD_MESSAGE_* constants and functions to query and generate\n" - "128-bit unique identifiers." -); - -PyDoc_STRVAR(randomize__doc__, - "randomize() -> UUID\n\n" - "Return a new random 128-bit unique identifier.\n" - "Wraps sd_id128_randomize(3)." -); - -PyDoc_STRVAR(get_machine__doc__, - "get_machine() -> UUID\n\n" - "Return a 128-bit unique identifier for this machine.\n" - "Wraps sd_id128_get_machine(3)." -); - -PyDoc_STRVAR(get_boot__doc__, - "get_boot() -> UUID\n\n" - "Return a 128-bit unique identifier for this boot.\n" - "Wraps sd_id128_get_boot(3)." -); - -static PyObject* make_uuid(sd_id128_t id) { - _cleanup_Py_DECREF_ PyObject - *uuid = NULL, *UUID = NULL, *bytes = NULL, - *args = NULL, *kwargs = NULL; - - uuid = PyImport_ImportModule("uuid"); - if (!uuid) - return NULL; - - UUID = PyObject_GetAttrString(uuid, "UUID"); - bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); - args = Py_BuildValue("()"); - kwargs = PyDict_New(); - if (!UUID || !bytes || !args || !kwargs) - return NULL; - - if (PyDict_SetItemString(kwargs, "bytes", bytes) < 0) - return NULL; - - return PyObject_Call(UUID, args, kwargs); -} - -#define helper(name) \ - static PyObject *name(PyObject *self, PyObject *args) { \ - sd_id128_t id; \ - int r; \ - \ - assert(args == NULL); \ - \ - r = sd_id128_##name(&id); \ - if (r < 0) { \ - errno = -r; \ - return PyErr_SetFromErrno(PyExc_IOError); \ - } \ - \ - return make_uuid(id); \ - } - -helper(randomize) -helper(get_machine) -helper(get_boot) - -static PyMethodDef methods[] = { - { "randomize", randomize, METH_NOARGS, randomize__doc__}, - { "get_machine", get_machine, METH_NOARGS, get_machine__doc__}, - { "get_boot", get_boot, METH_NOARGS, get_boot__doc__}, - { NULL, NULL, 0, NULL } /* Sentinel */ -}; - -static int add_id(PyObject *module, const char* name, sd_id128_t id) { - PyObject *obj; - - obj = make_uuid(id); - if (!obj) - return -1; - - return PyModule_AddObject(module, name, obj); -} - -#if PY_MAJOR_VERSION < 3 - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC initid128(void) { - PyObject *m; - - m = Py_InitModule3("id128", methods, module__doc__); - if (m == NULL) - return; - - /* a series of lines like 'add_id() ;' follow */ -#define JOINER ; -#include "id128-constants.h" -#undef JOINER - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); -} -REENABLE_WARNING; - -#else - -static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "id128", /* name of module */ - module__doc__, /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module */ - methods -}; - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC PyInit_id128(void) { - PyObject *m; - - m = PyModule_Create(&module); - if (m == NULL) - return NULL; - - if ( /* a series of lines like 'add_id() ||' follow */ -#define JOINER || -#include "id128-constants.h" -#undef JOINER - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { - Py_DECREF(m); - return NULL; - } - - return m; -} -REENABLE_WARNING; - -#endif diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py deleted file mode 100644 index dd1f229973..0000000000 --- a/src/python-systemd/journal.py +++ /dev/null @@ -1,548 +0,0 @@ -# -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2012 David Strauss <david@davidstrauss.net> -# Copyright 2012 Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> -# Copyright 2012 Marti Raudsepp <marti@juffo.org> -# -# systemd 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. -# -# systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. - -from __future__ import division - -import sys as _sys -import datetime as _datetime -import uuid as _uuid -import traceback as _traceback -import os as _os -import logging as _logging -if _sys.version_info >= (3,3): - from collections import ChainMap as _ChainMap -from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, - LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) -from ._journal import __version__, sendv, stream_fd -from ._reader import (_Reader, NOP, APPEND, INVALIDATE, - LOCAL_ONLY, RUNTIME_ONLY, - SYSTEM, SYSTEM_ONLY, CURRENT_USER, - _get_catalog) -from . import id128 as _id128 - -if _sys.version_info >= (3,): - from ._reader import Monotonic -else: - Monotonic = tuple - -def _convert_monotonic(m): - return Monotonic((_datetime.timedelta(microseconds=m[0]), - _uuid.UUID(bytes=m[1]))) - -def _convert_source_monotonic(s): - return _datetime.timedelta(microseconds=int(s)) - -def _convert_realtime(t): - return _datetime.datetime.fromtimestamp(t / 1000000) - -def _convert_timestamp(s): - return _datetime.datetime.fromtimestamp(int(s) / 1000000) - -def _convert_trivial(x): - return x - -if _sys.version_info >= (3,): - def _convert_uuid(s): - return _uuid.UUID(s.decode()) -else: - _convert_uuid = _uuid.UUID - -DEFAULT_CONVERTERS = { - 'MESSAGE_ID': _convert_uuid, - '_MACHINE_ID': _convert_uuid, - '_BOOT_ID': _convert_uuid, - 'PRIORITY': int, - 'LEADER': int, - 'SESSION_ID': int, - 'USERSPACE_USEC': int, - 'INITRD_USEC': int, - 'KERNEL_USEC': int, - '_UID': int, - '_GID': int, - '_PID': int, - 'SYSLOG_FACILITY': int, - 'SYSLOG_PID': int, - '_AUDIT_SESSION': int, - '_AUDIT_LOGINUID': int, - '_SYSTEMD_SESSION': int, - '_SYSTEMD_OWNER_UID': int, - 'CODE_LINE': int, - 'ERRNO': int, - 'EXIT_STATUS': int, - '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp, - '__REALTIME_TIMESTAMP': _convert_realtime, - '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic, - '__MONOTONIC_TIMESTAMP': _convert_monotonic, - '__CURSOR': _convert_trivial, - 'COREDUMP': bytes, - 'COREDUMP_PID': int, - 'COREDUMP_UID': int, - 'COREDUMP_GID': int, - 'COREDUMP_SESSION': int, - 'COREDUMP_SIGNAL': int, - 'COREDUMP_TIMESTAMP': _convert_timestamp, -} - -_IDENT_LETTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_') - -def _valid_field_name(s): - return not (set(s) - _IDENT_LETTER) - -class Reader(_Reader): - """Reader allows the access and filtering of systemd journal - entries. Note that in order to access the system journal, a - non-root user must be in the `systemd-journal` group. - - Example usage to print out all informational or higher level - messages for systemd-udevd for this boot: - - >>> j = journal.Reader() - >>> j.this_boot() - >>> j.log_level(journal.LOG_INFO) - >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service") - >>> for entry in j: - ... print(entry['MESSAGE']) - - See systemd.journal-fields(7) for more info on typical fields - found in the journal. - """ - def __init__(self, flags=0, path=None, files=None, converters=None): - """Create an instance of Reader, which allows filtering and - return of journal entries. - - Argument `flags` sets open flags of the journal, which can be one - of, or ORed combination of constants: LOCAL_ONLY (default) opens - journal on local machine only; RUNTIME_ONLY opens only - volatile journal files; and SYSTEM_ONLY opens only - journal files of system services and the kernel. - - Argument `path` is the directory of journal files. Note that - `flags` and `path` are exclusive. - - Argument `converters` is a dictionary which updates the - DEFAULT_CONVERTERS to convert journal field values. Field - names are used as keys into this dictionary. The values must - be single argument functions, which take a `bytes` object and - return a converted value. When there's no entry for a field - name, then the default UTF-8 decoding will be attempted. If - the conversion fails with a ValueError, unconverted bytes - object will be returned. (Note that ValueEror is a superclass - of UnicodeDecodeError). - - Reader implements the context manager protocol: the journal - will be closed when exiting the block. - """ - super(Reader, self).__init__(flags, path, files) - if _sys.version_info >= (3,3): - self.converters = _ChainMap() - if converters is not None: - self.converters.maps.append(converters) - self.converters.maps.append(DEFAULT_CONVERTERS) - else: - self.converters = DEFAULT_CONVERTERS.copy() - if converters is not None: - self.converters.update(converters) - - def _convert_field(self, key, value): - """Convert value using self.converters[key] - - If `key` is not present in self.converters, a standard unicode - decoding will be attempted. If the conversion (either - key-specific or the default one) fails with a ValueError, the - original bytes object will be returned. - """ - convert = self.converters.get(key, bytes.decode) - try: - return convert(value) - except ValueError: - # Leave in default bytes - return value - - def _convert_entry(self, entry): - """Convert entire journal entry utilising _covert_field""" - result = {} - for key, value in entry.items(): - if isinstance(value, list): - result[key] = [self._convert_field(key, val) for val in value] - else: - result[key] = self._convert_field(key, value) - return result - - def __iter__(self): - """Part of iterator protocol. - Returns self. - """ - return self - - def __next__(self): - """Part of iterator protocol. - Returns self.get_next() or raises StopIteration. - """ - ans = self.get_next() - if ans: - return ans - else: - raise StopIteration() - - if _sys.version_info < (3,): - next = __next__ - - def add_match(self, *args, **kwargs): - """Add one or more matches to the filter journal log entries. - All matches of different field are combined in a logical AND, - and matches of the same field are automatically combined in a - logical OR. - Matches can be passed as strings of form "FIELD=value", or - keyword arguments FIELD="value". - """ - args = list(args) - args.extend(_make_line(key, val) for key, val in kwargs.items()) - for arg in args: - super(Reader, self).add_match(arg) - - def get_next(self, skip=1): - """Return the next log entry as a mapping type, currently - a standard dictionary of fields. - - Optional skip value will return the `skip`\-th log entry. - - Entries will be processed with converters specified during - Reader creation. - """ - if super(Reader, self)._next(skip): - entry = super(Reader, self)._get_all() - if entry: - entry['__REALTIME_TIMESTAMP'] = self._get_realtime() - entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic() - entry['__CURSOR'] = self._get_cursor() - return self._convert_entry(entry) - return dict() - - def get_previous(self, skip=1): - """Return the previous log entry as a mapping type, - currently a standard dictionary of fields. - - Optional skip value will return the -`skip`\-th log entry. - - Entries will be processed with converters specified during - Reader creation. - - Equivalent to get_next(-skip). - """ - return self.get_next(-skip) - - def query_unique(self, field): - """Return unique values appearing in the journal for given `field`. - - Note this does not respect any journal matches. - - Entries will be processed with converters specified during - Reader creation. - """ - return set(self._convert_field(field, value) - for value in super(Reader, self).query_unique(field)) - - def wait(self, timeout=None): - """Wait for a change in the journal. `timeout` is the maximum - time in seconds to wait, or None, to wait forever. - - Returns one of NOP (no change), APPEND (new entries have been - added to the end of the journal), or INVALIDATE (journal files - have been added or removed). - """ - us = -1 if timeout is None else int(timeout * 1000000) - return super(Reader, self).wait(us) - - def seek_realtime(self, realtime): - """Seek to a matching journal entry nearest to `realtime` time. - - Argument `realtime` must be either an integer unix timestamp - or datetime.datetime instance. - """ - if isinstance(realtime, _datetime.datetime): - realtime = float(realtime.strftime("%s.%f")) * 1000000 - return super(Reader, self).seek_realtime(int(realtime)) - - def seek_monotonic(self, monotonic, bootid=None): - """Seek to a matching journal entry nearest to `monotonic` time. - - Argument `monotonic` is a timestamp from boot in either - seconds or a datetime.timedelta instance. Argument `bootid` - is a string or UUID representing which boot the monotonic time - is reference to. Defaults to current bootid. - """ - if isinstance(monotonic, _datetime.timedelta): - monotonic = monotonic.totalseconds() - monotonic = int(monotonic * 1000000) - if isinstance(bootid, _uuid.UUID): - bootid = bootid.hex - return super(Reader, self).seek_monotonic(monotonic, bootid) - - def log_level(self, level): - """Set maximum log `level` by setting matches for PRIORITY. - """ - if 0 <= level <= 7: - for i in range(level+1): - self.add_match(PRIORITY="%d" % i) - else: - raise ValueError("Log level must be 0 <= level <= 7") - - def messageid_match(self, messageid): - """Add match for log entries with specified `messageid`. - - `messageid` can be string of hexadicimal digits or a UUID - instance. Standard message IDs can be found in systemd.id128. - - Equivalent to add_match(MESSAGE_ID=`messageid`). - """ - if isinstance(messageid, _uuid.UUID): - messageid = messageid.hex - self.add_match(MESSAGE_ID=messageid) - - def this_boot(self, bootid=None): - """Add match for _BOOT_ID equal to current boot ID or the specified boot ID. - - If specified, bootid should be either a UUID or a 32 digit hex number. - - Equivalent to add_match(_BOOT_ID='bootid'). - """ - if bootid is None: - bootid = _id128.get_boot().hex - else: - bootid = getattr(bootid, 'hex', bootid) - self.add_match(_BOOT_ID=bootid) - - def this_machine(self, machineid=None): - """Add match for _MACHINE_ID equal to the ID of this machine. - - If specified, machineid should be either a UUID or a 32 digit hex number. - - Equivalent to add_match(_MACHINE_ID='machineid'). - """ - if machineid is None: - machineid = _id128.get_machine().hex - else: - machineid = getattr(machineid, 'hex', machineid) - self.add_match(_MACHINE_ID=machineid) - - -def get_catalog(mid): - if isinstance(mid, _uuid.UUID): - mid = mid.hex - return _get_catalog(mid) - -def _make_line(field, value): - if isinstance(value, bytes): - return field.encode('utf-8') + b'=' + value - elif isinstance(value, int): - return field + '=' + str(value) - else: - return field + '=' + value - -def send(MESSAGE, MESSAGE_ID=None, - CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, - **kwargs): - r"""Send a message to the journal. - - >>> journal.send('Hello world') - >>> journal.send('Hello, again, world', FIELD2='Greetings!') - >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') - - Value of the MESSAGE argument will be used for the MESSAGE= - field. MESSAGE must be a string and will be sent as UTF-8 to - the journal. - - MESSAGE_ID can be given to uniquely identify the type of - message. It must be a string or a uuid.UUID object. - - CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to - identify the caller. Unless at least on of the three is given, - values are extracted from the stack frame of the caller of - send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE - must be an integer. - - Additional fields for the journal entry can only be specified - as keyword arguments. The payload can be either a string or - bytes. A string will be sent as UTF-8, and bytes will be sent - as-is to the journal. - - Other useful fields include PRIORITY, SYSLOG_FACILITY, - SYSLOG_IDENTIFIER, SYSLOG_PID. - """ - - args = ['MESSAGE=' + MESSAGE] - - if MESSAGE_ID is not None: - id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID) - args.append('MESSAGE_ID=' + id) - - if CODE_LINE == CODE_FILE == CODE_FUNC == None: - CODE_FILE, CODE_LINE, CODE_FUNC = \ - _traceback.extract_stack(limit=2)[0][:3] - if CODE_FILE is not None: - args.append('CODE_FILE=' + CODE_FILE) - if CODE_LINE is not None: - args.append('CODE_LINE={:d}'.format(CODE_LINE)) - if CODE_FUNC is not None: - args.append('CODE_FUNC=' + CODE_FUNC) - - args.extend(_make_line(key, val) for key, val in kwargs.items()) - return sendv(*args) - -def stream(identifier, priority=LOG_DEBUG, level_prefix=False): - r"""Return a file object wrapping a stream to journal. - - Log messages written to this file as simple newline sepearted - text strings are written to the journal. - - The file will be line buffered, so messages are actually sent - after a newline character is written. - - >>> stream = journal.stream('myapp') - >>> stream - <open file '<fdopen>', mode 'w' at 0x...> - >>> stream.write('message...\n') - - will produce the following message in the journal:: - - PRIORITY=7 - SYSLOG_IDENTIFIER=myapp - MESSAGE=message... - - Using the interface with print might be more convinient: - - >>> from __future__ import print_function - >>> print('message...', file=stream) - - priority is the syslog priority, one of `LOG_EMERG`, - `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, - `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`. - - level_prefix is a boolean. If true, kernel-style log priority - level prefixes (such as '<1>') are interpreted. See - sd-daemon(3) for more information. - """ - - fd = stream_fd(identifier, priority, level_prefix) - return _os.fdopen(fd, 'w', 1) - -class JournalHandler(_logging.Handler): - """Journal handler class for the Python logging framework. - - Please see the Python logging module documentation for an - overview: http://docs.python.org/library/logging.html. - - To create a custom logger whose messages go only to journal: - - >>> log = logging.getLogger('custom_logger_name') - >>> log.propagate = False - >>> log.addHandler(journal.JournalHandler()) - >>> log.warn("Some message: %s", detail) - - Note that by default, message levels `INFO` and `DEBUG` are - ignored by the logging framework. To enable those log levels: - - >>> log.setLevel(logging.DEBUG) - - To redirect all logging messages to journal regardless of where - they come from, attach it to the root logger: - - >>> logging.root.addHandler(journal.JournalHandler()) - - For more complex configurations when using `dictConfig` or - `fileConfig`, specify `systemd.journal.JournalHandler` as the - handler class. Only standard handler configuration options - are supported: `level`, `formatter`, `filters`. - - To attach journal MESSAGE_ID, an extra field is supported: - - >>> import uuid - >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF') - >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid}) - - Fields to be attached to all messages sent through this - handler can be specified as keyword arguments. This probably - makes sense only for SYSLOG_IDENTIFIER and similar fields - which are constant for the whole program: - - >>> journal.JournalHandler(SYSLOG_IDENTIFIER='my-cool-app') - - The following journal fields will be sent: - `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, - `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call), - `MESSAGE_ID` (optional, see above), `SYSLOG_IDENTIFIER` (defaults - to sys.argv[0]). - """ - - def __init__(self, level=_logging.NOTSET, **kwargs): - super(JournalHandler, self).__init__(level) - - for name in kwargs: - if not _valid_field_name(name): - raise ValueError('Invalid field name: ' + name) - if 'SYSLOG_IDENTIFIER' not in kwargs: - kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0] - self._extra = kwargs - - def emit(self, record): - """Write record as journal event. - - MESSAGE is taken from the message provided by the - user, and PRIORITY, LOGGER, THREAD_NAME, - CODE_{FILE,LINE,FUNC} fields are appended - automatically. In addition, record.MESSAGE_ID will be - used if present. - """ - try: - msg = self.format(record) - pri = self.mapPriority(record.levelno) - mid = getattr(record, 'MESSAGE_ID', None) - send(msg, - MESSAGE_ID=mid, - PRIORITY=format(pri), - LOGGER=record.name, - THREAD_NAME=record.threadName, - CODE_FILE=record.pathname, - CODE_LINE=record.lineno, - CODE_FUNC=record.funcName, - **self._extra) - except Exception: - self.handleError(record) - - @staticmethod - def mapPriority(levelno): - """Map logging levels to journald priorities. - - Since Python log level numbers are "sparse", we have - to map numbers in between the standard levels too. - """ - if levelno <= _logging.DEBUG: - return LOG_DEBUG - elif levelno <= _logging.INFO: - return LOG_INFO - elif levelno <= _logging.WARNING: - return LOG_WARNING - elif levelno <= _logging.ERROR: - return LOG_ERR - elif levelno <= _logging.CRITICAL: - return LOG_CRIT - else: - return LOG_ALERT diff --git a/src/python-systemd/login.c b/src/python-systemd/login.c deleted file mode 100644 index e844f5fc69..0000000000 --- a/src/python-systemd/login.c +++ /dev/null @@ -1,376 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#define PY_SSIZE_T_CLEAN -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wredundant-decls" -#include <Python.h> -#pragma GCC diagnostic pop - -#include "systemd/sd-login.h" -#include "pyutil.h" -#include "util.h" -#include "strv.h" - -PyDoc_STRVAR(module__doc__, - "Python interface to the libsystemd-login library." -); - -#define helper(name) \ -static PyObject* name(PyObject *self, PyObject *args) { \ - _cleanup_strv_free_ char **list = NULL; \ - int r; \ - PyObject *ans; \ - \ - assert(args == NULL); \ - \ - r = sd_get_##name(&list); \ - if (r < 0) { \ - errno = -r; \ - return PyErr_SetFromErrno(PyExc_IOError); \ - } \ - \ - ans = PyList_New(r); \ - if (!ans) \ - return NULL; \ - \ - for (r--; r >= 0; r--) { \ - PyObject *s = unicode_FromString(list[r]); \ - if (!s) { \ - Py_DECREF(ans); \ - return NULL; \ - } \ - \ - PyList_SetItem(ans, r, s); \ - } \ - \ - return ans; \ -} - -helper(seats) -helper(sessions) -helper(machine_names) -#undef helper - -static PyObject* uids(PyObject *self, PyObject *args) { - _cleanup_free_ uid_t *list = NULL; - int r; - PyObject *ans; - - assert(args == NULL); - - r = sd_get_uids(&list); - if (r < 0) { - errno = -r; - return PyErr_SetFromErrno(PyExc_IOError); - } - - ans = PyList_New(r); - if (!ans) - return NULL; - - for (r--; r >= 0; r--) { - PyObject *s = long_FromLong(list[r]); - if (!s) { - Py_DECREF(ans); - return NULL; - } - - PyList_SetItem(ans, r, s); - } - - return ans; -} - -PyDoc_STRVAR(seats__doc__, - "seats() -> list\n\n" - "Returns a list of currently available local seats.\n" - "Wraps sd_get_seats(3)." -); - -PyDoc_STRVAR(sessions__doc__, - "sessions() -> list\n\n" - "Returns a list of current login sessions.\n" - "Wraps sd_get_sessions(3)." -); - -PyDoc_STRVAR(machine_names__doc__, - "machine_names() -> list\n\n" - "Returns a list of currently running virtual machines\n" - "and containers on the system.\n" - "Wraps sd_get_machine_names(3)." -); - -PyDoc_STRVAR(uids__doc__, - "uids() -> list\n\n" - "Returns a list of uids of users who currently have login sessions.\n" - "Wraps sd_get_uids(3)." -); - -static PyMethodDef methods[] = { - { "seats", seats, METH_NOARGS, seats__doc__}, - { "sessions", sessions, METH_NOARGS, sessions__doc__}, - { "machine_names", machine_names, METH_NOARGS, machine_names__doc__}, - { "uids", uids, METH_NOARGS, uids__doc__}, - {} /* Sentinel */ -}; - - -typedef struct { - PyObject_HEAD - sd_login_monitor *monitor; -} Monitor; -static PyTypeObject MonitorType; - -static void Monitor_dealloc(Monitor* self) { - sd_login_monitor_unref(self->monitor); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -PyDoc_STRVAR(Monitor__doc__, - "Monitor([category]) -> ...\n\n" - "Monitor may be used to monitor login sessions, users, seats,\n" - "and virtual machines/containers. Monitor provides a file\n" - "descriptor which can be integrated in an external event loop.\n" - "See man:sd_login_monitor_new(3) for the details about what\n" - "can be monitored."); -static int Monitor_init(Monitor *self, PyObject *args, PyObject *keywds) { - const char *category = NULL; - int r; - - static const char* const kwlist[] = {"category", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|z:__init__", (char**) kwlist, - &category)) - return -1; - - Py_BEGIN_ALLOW_THREADS - r = sd_login_monitor_new(category, &self->monitor); - Py_END_ALLOW_THREADS - - return set_error(r, NULL, "Invalid category"); -} - - -PyDoc_STRVAR(Monitor_fileno__doc__, - "fileno() -> int\n\n" - "Get a file descriptor to poll for events.\n" - "This method wraps sd_login_monitor_get_fd(3)."); -static PyObject* Monitor_fileno(Monitor *self, PyObject *args) { - int fd = sd_login_monitor_get_fd(self->monitor); - set_error(fd, NULL, NULL); - if (fd < 0) - return NULL; - return long_FromLong(fd); -} - - -PyDoc_STRVAR(Monitor_get_events__doc__, - "get_events() -> int\n\n" - "Returns a mask of poll() events to wait for on the file\n" - "descriptor returned by .fileno().\n\n" - "See man:sd_login_monitor_get_events(3) for further discussion."); -static PyObject* Monitor_get_events(Monitor *self, PyObject *args) { - int r = sd_login_monitor_get_events(self->monitor); - set_error(r, NULL, NULL); - if (r < 0) - return NULL; - return long_FromLong(r); -} - - -PyDoc_STRVAR(Monitor_get_timeout__doc__, - "get_timeout() -> int or None\n\n" - "Returns a timeout value for usage in poll(), the time since the\n" - "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" - "is necessary.\n\n" - "The return value must be converted to a relative timeout in\n" - "milliseconds if it is to be used as an argument for poll().\n" - "See man:sd_login_monitor_get_timeout(3) for further discussion."); -static PyObject* Monitor_get_timeout(Monitor *self, PyObject *args) { - int r; - uint64_t t; - - r = sd_login_monitor_get_timeout(self->monitor, &t); - set_error(r, NULL, NULL); - if (r < 0) - return NULL; - - if (t == (uint64_t) -1) - Py_RETURN_NONE; - - assert_cc(sizeof(unsigned long long) == sizeof(t)); - return PyLong_FromUnsignedLongLong(t); -} - - -PyDoc_STRVAR(Monitor_get_timeout_ms__doc__, - "get_timeout_ms() -> int\n\n" - "Returns a timeout value suitable for usage in poll(), the value\n" - "returned by .get_timeout() converted to relative ms, or -1 if\n" - "no timeout is necessary."); -static PyObject* Monitor_get_timeout_ms(Monitor *self, PyObject *args) { - int r; - uint64_t t; - - r = sd_login_monitor_get_timeout(self->monitor, &t); - set_error(r, NULL, NULL); - if (r < 0) - return NULL; - - return absolute_timeout(t); -} - - -PyDoc_STRVAR(Monitor_close__doc__, - "close() -> None\n\n" - "Free resources allocated by this Monitor object.\n" - "This method invokes sd_login_monitor_unref().\n" - "See man:sd_login_monitor_unref(3)."); -static PyObject* Monitor_close(Monitor *self, PyObject *args) { - assert(self); - assert(!args); - - sd_login_monitor_unref(self->monitor); - self->monitor = NULL; - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Monitor_flush__doc__, - "flush() -> None\n\n" - "Reset the wakeup state of the monitor object.\n" - "This method invokes sd_login_monitor_flush().\n" - "See man:sd_login_monitor_flush(3)."); -static PyObject* Monitor_flush(Monitor *self, PyObject *args) { - assert(self); - assert(!args); - - Py_BEGIN_ALLOW_THREADS - sd_login_monitor_flush(self->monitor); - Py_END_ALLOW_THREADS - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Monitor___enter____doc__, - "__enter__() -> self\n\n" - "Part of the context manager protocol.\n" - "Returns self.\n"); -static PyObject* Monitor___enter__(PyObject *self, PyObject *args) { - assert(self); - assert(!args); - - Py_INCREF(self); - return self; -} - - -PyDoc_STRVAR(Monitor___exit____doc__, - "__exit__(type, value, traceback) -> None\n\n" - "Part of the context manager protocol.\n" - "Closes the monitor..\n"); -static PyObject* Monitor___exit__(Monitor *self, PyObject *args) { - return Monitor_close(self, args); -} - - -static PyMethodDef Monitor_methods[] = { - {"fileno", (PyCFunction) Monitor_fileno, METH_NOARGS, Monitor_fileno__doc__}, - {"get_events", (PyCFunction) Monitor_get_events, METH_NOARGS, Monitor_get_events__doc__}, - {"get_timeout", (PyCFunction) Monitor_get_timeout, METH_NOARGS, Monitor_get_timeout__doc__}, - {"get_timeout_ms", (PyCFunction) Monitor_get_timeout_ms, METH_NOARGS, Monitor_get_timeout_ms__doc__}, - {"close", (PyCFunction) Monitor_close, METH_NOARGS, Monitor_close__doc__}, - {"flush", (PyCFunction) Monitor_flush, METH_NOARGS, Monitor_flush__doc__}, - {"__enter__", (PyCFunction) Monitor___enter__, METH_NOARGS, Monitor___enter____doc__}, - {"__exit__", (PyCFunction) Monitor___exit__, METH_VARARGS, Monitor___exit____doc__}, - {} /* Sentinel */ -}; - -static PyTypeObject MonitorType = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "login.Monitor", - .tp_basicsize = sizeof(Monitor), - .tp_dealloc = (destructor) Monitor_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_doc = Monitor__doc__, - .tp_methods = Monitor_methods, - .tp_init = (initproc) Monitor_init, - .tp_new = PyType_GenericNew, -}; - -#if PY_MAJOR_VERSION < 3 - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC initlogin(void) { - PyObject *m; - - if (PyType_Ready(&MonitorType) < 0) - return; - - m = Py_InitModule3("login", methods, module__doc__); - if (m == NULL) - return; - - PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); - - Py_INCREF(&MonitorType); - PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType); -} -REENABLE_WARNING; - -#else - -static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "login", /* name of module */ - module__doc__, /* module documentation, may be NULL */ - -1, /* size of per-interpreter state of the module */ - methods -}; - -DISABLE_WARNING_MISSING_PROTOTYPES; -PyMODINIT_FUNC PyInit_login(void) { - PyObject *m; - - if (PyType_Ready(&MonitorType) < 0) - return NULL; - - m = PyModule_Create(&module); - if (m == NULL) - return NULL; - - if (PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { - Py_DECREF(m); - return NULL; - } - - Py_INCREF(&MonitorType); - if (PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType)) { - Py_DECREF(&MonitorType); - Py_DECREF(m); - return NULL; - } - - return m; -} -REENABLE_WARNING; - -#endif diff --git a/src/python-systemd/pyutil.c b/src/python-systemd/pyutil.c deleted file mode 100644 index 722c4f5b5f..0000000000 --- a/src/python-systemd/pyutil.c +++ /dev/null @@ -1,80 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#include <Python.h> -#include "pyutil.h" - -void cleanup_Py_DECREFp(PyObject **p) { - if (!*p) - return; - - Py_DECREF(*p); -} - -PyObject* absolute_timeout(uint64_t t) { - if (t == (uint64_t) -1) - return PyLong_FromLong(-1); - else { - struct timespec ts; - uint64_t n; - int msec; - - clock_gettime(CLOCK_MONOTONIC, &ts); - n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; - msec = t > n ? (int) ((t - n + 999) / 1000) : 0; - - return PyLong_FromLong(msec); - } -} - -int set_error(int r, const char* path, const char* invalid_message) { - if (r >= 0) - return r; - if (r == -EINVAL && invalid_message) - PyErr_SetString(PyExc_ValueError, invalid_message); - else if (r == -ENOMEM) - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - else { - errno = -r; - PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); - } - return -1; -} - -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 -int Unicode_FSConverter(PyObject* obj, void *_result) { - PyObject **result = _result; - - assert(result); - - if (!obj) - /* cleanup: we don't return Py_CLEANUP_SUPPORTED, so - * we can assume that it was PyUnicode_FSConverter. */ - return PyUnicode_FSConverter(obj, result); - - if (obj == Py_None) { - *result = NULL; - return 1; - } - - return PyUnicode_FSConverter(obj, result); -} -#endif diff --git a/src/python-systemd/pyutil.h b/src/python-systemd/pyutil.h deleted file mode 100644 index 1477e7bf9c..0000000000 --- a/src/python-systemd/pyutil.h +++ /dev/null @@ -1,54 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Zbigniew JÄ™drzejewski-Szmek <zbyszek@in.waw.pl> - - systemd 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. - - systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. -***/ - -#ifndef Py_TYPE -/* avoid duplication warnings from errors in Python 2.7 headers */ -# include <Python.h> -#endif - -void cleanup_Py_DECREFp(PyObject **p); -PyObject* absolute_timeout(uint64_t t); -int set_error(int r, const char* path, const char* invalid_message); - -#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 -int Unicode_FSConverter(PyObject* obj, void *_result); -#endif - -#define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) - -#if PY_MAJOR_VERSION >=3 -# define unicode_FromStringAndSize PyUnicode_FromStringAndSize -# define unicode_FromString PyUnicode_FromString -# define long_FromLong PyLong_FromLong -# define long_FromSize_t PyLong_FromSize_t -# define long_Check PyLong_Check -# define long_AsLong PyLong_AsLong -#else -/* Python 3 type naming convention is used */ -# define unicode_FromStringAndSize PyString_FromStringAndSize -# define unicode_FromString PyString_FromString -# define long_FromLong PyInt_FromLong -# define long_FromSize_t PyInt_FromSize_t -# define long_Check PyInt_Check -# define long_AsLong PyInt_AsLong -#endif diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c index f9448e3bc5..0edba415b6 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve-host/resolve-host.c @@ -89,10 +89,6 @@ static int resolve_host(sd_bus *bus, const char *name) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - return bus_log_create_error(r); - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); if (r < 0) return bus_log_create_error(r); diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index a3e740896f..63b4b36e88 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -43,3 +43,8 @@ int dns_type_from_string(const char *s) { return sc->id; } + +/* XXX: find an authoritative list of all pseudo types? */ +bool dns_type_is_pseudo(int n) { + return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT); +} diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 86951d233a..950af36ee3 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -25,6 +25,7 @@ const char *dns_type_to_string(int type); int dns_type_from_string(const char *s); +bool dns_type_is_pseudo(int n); /* DNS record types, taken from * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index bb74b1828e..39951a362c 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -32,10 +32,10 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { assert(ret); - if (mtu <= 0) + if (mtu <= UDP_PACKET_HEADER_SIZE) a = DNS_PACKET_SIZE_START; else - a = mtu; + a = mtu - UDP_PACKET_HEADER_SIZE; if (a < DNS_PACKET_HEADER_SIZE) a = DNS_PACKET_HEADER_SIZE; @@ -166,10 +166,17 @@ int dns_packet_validate_reply(DnsPacket *p) { if (DNS_PACKET_OPCODE(p) != 0) return -EBADMSG; - /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ - if (p->protocol == DNS_PROTOCOL_LLMNR && - DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + break; + + default: + break; + } return 1; } @@ -192,18 +199,25 @@ int dns_packet_validate_query(DnsPacket *p) { if (DNS_PACKET_TC(p)) return -EBADMSG; - /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ - if (p->protocol == DNS_PROTOCOL_LLMNR && - DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; - /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ - if (DNS_PACKET_ANCOUNT(p) > 0) - return -EBADMSG; + /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ + if (DNS_PACKET_ANCOUNT(p) > 0) + return -EBADMSG; - /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ - if (DNS_PACKET_NSCOUNT(p) > 0) - return -EBADMSG; + /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ + if (DNS_PACKET_NSCOUNT(p) > 0) + return -EBADMSG; + + break; + + default: + break; + } return 1; } @@ -261,7 +275,7 @@ static void dns_packet_truncate(DnsPacket *p, size_t sz) { if (p->size <= sz) return; - HASHMAP_FOREACH_KEY(s, n, p->names, i) { + HASHMAP_FOREACH_KEY(n, s, p->names, i) { if (PTR_TO_SIZE(n) < sz) continue; @@ -488,6 +502,82 @@ fail: return r; } +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(types); + assert(length > 0); + + saved_size = p->size; + + r = dns_packet_append_uint8(p, window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, length, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, types, length, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { + Iterator i; + uint8_t window = 0; + uint8_t entry = 0; + uint8_t bitmaps[32] = {}; + unsigned n; + size_t saved_size; + int r; + + assert(p); + assert(types); + + saved_size = p->size; + + BITMAP_FOREACH(n, types, i) { + assert(n <= 0xffff); + + if ((n >> 8) != window && bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + + zero(bitmaps); + } + + window = n >> 8; + + entry = n & 255; + + bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); + } + + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) { size_t saved_size, rdlength_offset, end, rdlength; int r; @@ -638,6 +728,22 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); break; + case DNS_TYPE_DS: + r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); + break; + case DNS_TYPE_SSHFP: r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); if (r < 0) @@ -647,7 +753,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_blob(p, rr->sshfp.key, rr->sshfp.key_size, NULL); + r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); break; case DNS_TYPE_DNSKEY: @@ -691,7 +797,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_uint8(p, rr->rrsig.key_tag, NULL); + r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); if (r < 0) goto fail; @@ -702,6 +808,50 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); break; + case DNS_TYPE_NSEC: + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec.types, NULL); + if (r < 0) + goto fail; + + break; + case DNS_TYPE_NSEC3: + r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec3.types, NULL); + if (r < 0) + goto fail; + + break; case _DNS_TYPE_INVALID: /* unparseable */ default: @@ -775,6 +925,42 @@ int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { return 0; } +static int dns_packet_read_memdup( + DnsPacket *p, size_t size, + void **ret, size_t *ret_size, + size_t *ret_start) { + + const void *src; + size_t start; + int r; + + assert(p); + assert(ret); + + r = dns_packet_read(p, size, &src, &start); + if (r < 0) + return r; + + if (size <= 0) + *ret = NULL; + else { + void *copy; + + copy = memdup(src, size); + if (!copy) + return -ENOMEM; + + *ret = copy; + } + + if (ret_size) + *ret_size = size; + if (ret_start) + *ret_start = start; + + return 0; +} + int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { const void *d; int r; @@ -966,6 +1152,115 @@ fail: return r; } +static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { + uint8_t window; + uint8_t length; + const uint8_t *bitmap; + uint8_t bit = 0; + unsigned i; + bool found = false; + size_t saved_rindex; + int r; + + assert(p); + assert(types); + + saved_rindex = p->rindex; + + r = bitmap_ensure_allocated(types); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &length, NULL); + if (r < 0) + goto fail; + + if (length == 0 || length > 32) + return -EBADMSG; + + r = dns_packet_read(p, length, (const void **)&bitmap, NULL); + if (r < 0) + goto fail; + + for (i = 0; i < length; i++) { + uint8_t bitmask = 1 << 7; + + if (!bitmap[i]) { + found = false; + bit += 8; + continue; + } + + found = true; + + while (bitmask) { + if (bitmap[i] & bitmask) { + uint16_t n; + + n = (uint16_t) window << 8 | (uint16_t) bit; + + /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ + if (dns_type_is_pseudo(n)) + continue; + + r = bitmap_set(*types, n); + if (r < 0) + goto fail; + } + + bit ++; + bitmask >>= 1; + } + } + + if (!found) + return -EBADMSG; + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { + size_t saved_rindex; + int r; + + saved_rindex = p->rindex; + + while (p->rindex < saved_rindex + size) { + r = dns_packet_read_type_window(p, types, NULL); + if (r < 0) + goto fail; + + /* don't read past end of current RR */ + if (p->rindex > saved_rindex + size) { + r = -EBADMSG; + goto fail; + } + } + + if (p->rindex != saved_rindex + size) { + r = -EBADMSG; + goto fail; + } + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { _cleanup_free_ char *name = NULL; uint16_t class, type; @@ -1008,26 +1303,6 @@ fail: return r; } -static int dns_packet_read_public_key(DnsPacket *p, size_t length, - void **dp, size_t *lengthp, - size_t *start) { - int r; - const void *d; - void *d2; - - r = dns_packet_read(p, length, &d, NULL); - if (r < 0) - return r; - - d2 = memdup(d, length); - if (!d2) - return -ENOMEM; - - *dp = d2; - *lengthp = length; - return 0; -} - static bool loc_size_ok(uint8_t size) { uint8_t m = size >> 4, e = size & 0xF; @@ -1050,7 +1325,6 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; size_t saved_rindex, offset; uint16_t rdlength; - const void *d; int r; assert(p); @@ -1248,6 +1522,33 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { } } + case DNS_TYPE_DS: + r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->ds.digest, &rr->ds.digest_size, + NULL); + if (r < 0) + goto fail; + + if (rr->ds.digest_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; case DNS_TYPE_SSHFP: r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); if (r < 0) @@ -1257,9 +1558,17 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - r = dns_packet_read_public_key(p, rdlength - 2, - &rr->sshfp.key, &rr->sshfp.key_size, - NULL); + r = dns_packet_read_memdup(p, rdlength - 2, + &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, + NULL); + + if (rr->sshfp.fingerprint_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + break; case DNS_TYPE_DNSKEY: { @@ -1288,9 +1597,17 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - r = dns_packet_read_public_key(p, rdlength - 4, - &rr->dnskey.key, &rr->dnskey.key_size, - NULL); + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + + if (rr->dnskey.key_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + break; } @@ -1327,24 +1644,87 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - r = dns_packet_read_public_key(p, offset + rdlength - p->rindex, - &rr->rrsig.signature, &rr->rrsig.signature_size, - NULL); + r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + + if (rr->rrsig.signature_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + break; - default: - unparseable: - r = dns_packet_read(p, rdlength, &d, NULL); + case DNS_TYPE_NSEC: + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, false, NULL); if (r < 0) goto fail; - rr->generic.data = memdup(d, rdlength); - if (!rr->generic.data) { - r = -ENOMEM; + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + if (r < 0) + goto fail; + + /* The types bitmap must contain at least the NSEC record itself, so an empty bitmap means + something went wrong */ + if (bitmap_isclear(rr->nsec.types)) { + r = -EBADMSG; goto fail; } - rr->generic.size = rdlength; + break; + + case DNS_TYPE_NSEC3: { + uint8_t size; + + r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + /* this may be zero */ + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + goto fail; + + if (size <= 0) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + if (r < 0) + goto fail; + + /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ + + break; + } + default: + unparseable: + r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL); + if (r < 0) + goto fail; break; } if (r < 0) @@ -1466,13 +1846,15 @@ static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { - [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", - [DNSSEC_ALGORITHM_DH] = "DH", - [DNSSEC_ALGORITHM_DSA] = "DSA", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", - [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", - [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", + [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", + [DNSSEC_ALGORITHM_DH] = "DH", + [DNSSEC_ALGORITHM_DSA] = "DSA", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", + [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index c5867386c6..58559c85df 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -21,6 +21,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <netinet/udp.h> +#include <netinet/ip.h> #include "macro.h" #include "sparse-endian.h" @@ -53,6 +55,7 @@ struct DnsPacketHeader { }; #define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) +#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) /* The various DNS protocols deviate in how large a packet can grow, but the TCP transport has a 16bit size field, hence that appears to @@ -99,10 +102,18 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { #define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id #define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) #define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) -#define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) +#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) #define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) -#define DNS_PACKET_C(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) -#define DNS_PACKET_T(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) +#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) +#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) +#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) +#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) +#define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) + +/* LLMNR defines some bits differently */ +#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) +#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) + #define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) #define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) #define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) @@ -212,6 +223,8 @@ enum { DNSSEC_ALGORITHM_DSA, DNSSEC_ALGORITHM_ECC, DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, DNSSEC_ALGORITHM_INDIRECT = 252, DNSSEC_ALGORITHM_PRIVATEDNS, DNSSEC_ALGORITHM_PRIVATEOID, diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 4d71f5e3d4..0efe740d1a 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -188,6 +188,46 @@ int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other) { return 1; } +int dns_question_contains(DnsQuestion *a, DnsResourceKey *k) { + unsigned j; + int r; + + assert(a); + assert(k); + + for (j = 0; j < a->n_keys; j++) { + r = dns_resource_key_equal(a->keys[j], k); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { + unsigned j; + int r; + + assert(a); + assert(b); + + /* Checks if all keys in a are also contained b, and vice versa */ + + for (j = 0; j < a->n_keys; j++) { + r = dns_question_contains(b, a->keys[j]); + if (r <= 0) + return r; + } + + for (j = 0; j < b->n_keys; j++) { + r = dns_question_contains(a, b->keys[j]); + if (r <= 0) + return r; + } + + return 1; +} + int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret) { _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; bool same = true; diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index 4ba2fe9f0e..fc98677798 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -43,6 +43,8 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr); int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr); int dns_question_is_valid(DnsQuestion *q); int dns_question_is_superset(DnsQuestion *q, DnsQuestion *other); +int dns_question_contains(DnsQuestion *a, DnsResourceKey *k); +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); int dns_question_cname_redirect(DnsQuestion *q, const char *name, DnsQuestion **ret); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index c1818eef9c..ad7ca26cfe 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -171,19 +171,19 @@ const struct hash_ops dns_resource_key_hash_ops = { }; int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { - char cbuf[DECIMAL_STR_MAX(uint16_t)], tbuf[DECIMAL_STR_MAX(uint16_t)]; + char cbuf[strlen("CLASS") + DECIMAL_STR_MAX(uint16_t)], tbuf[strlen("TYPE") + DECIMAL_STR_MAX(uint16_t)]; const char *c, *t; char *s; c = dns_class_to_string(key->class); if (!c) { - sprintf(cbuf, "%i", key->class); + sprintf(cbuf, "CLASS%u", key->class); c = cbuf; } t = dns_type_to_string(key->type); if (!t){ - sprintf(tbuf, "%i", key->type); + sprintf(tbuf, "TYPE%u", key->type); t = tbuf; } @@ -271,8 +271,12 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { free(rr->mx.exchange); break; + case DNS_TYPE_DS: + free(rr->ds.digest); + break; + case DNS_TYPE_SSHFP: - free(rr->sshfp.key); + free(rr->sshfp.fingerprint); break; case DNS_TYPE_DNSKEY: @@ -284,6 +288,17 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { free(rr->rrsig.signature); break; + case DNS_TYPE_NSEC: + free(rr->nsec.next_domain_name); + bitmap_free(rr->nsec.types); + break; + + case DNS_TYPE_NSEC3: + free(rr->nsec3.next_hashed_name); + free(rr->nsec3.salt); + bitmap_free(rr->nsec3.types); + break; + case DNS_TYPE_LOC: case DNS_TYPE_A: case DNS_TYPE_AAAA: @@ -409,11 +424,18 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor a->loc.longitude == b->loc.longitude && a->loc.altitude == b->loc.altitude; + case DNS_TYPE_DS: + return a->ds.key_tag == b->ds.key_tag && + a->ds.algorithm == b->ds.algorithm && + a->ds.digest_type == b->ds.digest_type && + a->ds.digest_size == b->ds.digest_size && + memcmp(a->ds.digest, b->ds.digest, a->ds.digest_size) == 0; + case DNS_TYPE_SSHFP: return a->sshfp.algorithm == b->sshfp.algorithm && a->sshfp.fptype == b->sshfp.fptype && - a->sshfp.key_size == b->sshfp.key_size && - memcmp(a->sshfp.key, b->sshfp.key, a->sshfp.key_size) == 0; + a->sshfp.fingerprint_size == b->sshfp.fingerprint_size && + memcmp(a->sshfp.fingerprint, b->sshfp.fingerprint, a->sshfp.fingerprint_size) == 0; case DNS_TYPE_DNSKEY: return a->dnskey.zone_key_flag == b->dnskey.zone_key_flag && @@ -437,6 +459,19 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor return dns_name_equal(a->rrsig.signer, b->rrsig.signer); + case DNS_TYPE_NSEC: + return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && + bitmap_equal(a->nsec.types, b->nsec.types); + + case DNS_TYPE_NSEC3: + return a->nsec3.algorithm == b->nsec3.algorithm && + a->nsec3.flags == b->nsec3.flags && + a->nsec3.iterations == b->nsec3.iterations && + a->nsec3.salt_size == b->nsec3.salt_size && + memcmp(a->nsec3.salt, b->nsec3.salt, a->nsec3.salt_size) == 0 && + memcmp(a->nsec3.next_hashed_name, b->nsec3.next_hashed_name, a->nsec3.next_hashed_name_size) == 0 && + bitmap_equal(a->nsec3.types, b->nsec3.types); + default: return a->generic.size == b->generic.size && memcmp(a->generic.data, b->generic.data, a->generic.size) == 0; @@ -474,6 +509,53 @@ static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t alt return s; } +static int format_timestamp_dns(char *buf, size_t l, time_t sec) { + struct tm tm; + + assert(buf); + assert(l > strlen("YYYYMMDDHHmmSS")); + + if (!gmtime_r(&sec, &tm)) + return -EINVAL; + + if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) + return -EINVAL; + + return 0; +} + +static char *format_types(Bitmap *types) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_free_ char *str = NULL; + Iterator i; + unsigned type; + int r; + + BITMAP_FOREACH(type, types, i) { + if (dns_type_to_string(type)) { + r = strv_extend(&strv, dns_type_to_string(type)); + if (r < 0) + return NULL; + } else { + char *t; + + r = asprintf(&t, "TYPE%u", type); + if (r < 0) + return NULL; + + r = strv_consume(&strv, t); + if (r < 0) + return NULL; + } + } + + str = strv_join(strv, " "); + if (!str) + return NULL; + + return strjoin("( ", str, " )", NULL); +} + int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { _cleanup_free_ char *k = NULL, *t = NULL; char *s; @@ -589,8 +671,23 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { return -ENOMEM; break; + case DNS_TYPE_DS: + t = hexmem(rr->ds.digest, rr->ds.digest_size); + if (!t) + return -ENOMEM; + + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->ds.key_tag, + rr->ds.algorithm, + rr->ds.digest_type, + t); + if (r < 0) + return -ENOMEM; + break; + case DNS_TYPE_SSHFP: - t = hexmem(rr->sshfp.key, rr->sshfp.key_size); + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); if (!t) return -ENOMEM; @@ -608,7 +705,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { alg = dnssec_algorithm_to_string(rr->dnskey.algorithm); - t = hexmem(rr->dnskey.key, rr->dnskey.key_size); + t = base64mem(rr->dnskey.key, rr->dnskey.key_size); if (!t) return -ENOMEM; @@ -625,18 +722,27 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { case DNS_TYPE_RRSIG: { const char *type, *alg; + char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; type = dns_type_to_string(rr->rrsig.type_covered); alg = dnssec_algorithm_to_string(rr->rrsig.algorithm); - t = hexmem(rr->rrsig.signature, rr->rrsig.signature_size); + t = base64mem(rr->rrsig.signature, rr->rrsig.signature_size); if (!t) return -ENOMEM; + r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); + if (r < 0) + return r; + + r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); + if (r < 0) + return r; + /* TYPE?? follows * http://tools.ietf.org/html/rfc3597#section-5 */ - r = asprintf(&s, "%s %s%.*u %.*s%.*u %u %u %u %u %u %s %s", + r = asprintf(&s, "%s %s%.*u %.*s%.*u %u %u %s %s %u %s %s", k, type ?: "TYPE", type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, @@ -644,8 +750,8 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { alg ? 0 : 1, alg ? 0u : (unsigned) rr->rrsig.algorithm, rr->rrsig.labels, rr->rrsig.original_ttl, - rr->rrsig.expiration, - rr->rrsig.inception, + expiration, + inception, rr->rrsig.key_tag, rr->rrsig.signer, t); @@ -654,13 +760,57 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { break; } + case DNS_TYPE_NSEC: + t = format_types(rr->nsec.types); + if (!t) + return -ENOMEM; + + r = asprintf(&s, "%s %s %s", + k, + rr->nsec.next_domain_name, + t); + if (r < 0) + return -ENOMEM; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *salt = NULL, *hash = NULL; + + if (rr->nsec3.salt_size > 0) { + salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); + if (!salt) + return -ENOMEM; + } + + hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); + if (!hash) + return -ENOMEM; + + t = format_types(rr->nsec3.types); + if (!t) + return -ENOMEM; + + r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", + k, + rr->nsec3.algorithm, + rr->nsec3.flags, + rr->nsec3.iterations, + rr->nsec3.salt_size > 0 ? salt : "-", + hash, + t); + if (r < 0) + return -ENOMEM; + + break; + } + default: t = hexmem(rr->generic.data, rr->generic.size); if (!t) return -ENOMEM; - s = strjoin(k, " ", t, NULL); - if (!s) + r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.size, t); + if (r < 0) return -ENOMEM; break; } @@ -690,7 +840,7 @@ int dns_class_from_string(const char *s, uint16_t *class) { if (strcaseeq(s, "IN")) *class = DNS_CLASS_IN; else if (strcaseeq(s, "ANY")) - *class = DNS_TYPE_ANY; + *class = DNS_CLASS_ANY; else return -EINVAL; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 26796c842b..0f40f3ceef 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -23,6 +23,7 @@ #include <netinet/in.h> +#include "bitmap.h" #include "hashmap.h" #include "in-addr-util.h" #include "dns-type.h" @@ -52,7 +53,7 @@ struct DnsResourceRecord { union { struct { void *data; - uint16_t size; + size_t size; } generic; struct { @@ -109,10 +110,19 @@ struct DnsResourceRecord { } loc; struct { + uint16_t key_tag; + uint8_t algorithm; + uint8_t digest_type; + void *digest; + size_t digest_size; + } ds; + + /* https://tools.ietf.org/html/rfc4255#section-3.1 */ + struct { uint8_t algorithm; uint8_t fptype; - void *key; - size_t key_size; + void *fingerprint; + size_t fingerprint_size; } sshfp; /* http://tools.ietf.org/html/rfc4034#section-2.1 */ @@ -137,6 +147,22 @@ struct DnsResourceRecord { void *signature; size_t signature_size; } rrsig; + + struct { + char *next_domain_name; + Bitmap *types; + } nsec; + + struct { + uint8_t algorithm; + uint8_t flags; + uint16_t iterations; + void *salt; + size_t salt_size; + void *next_hashed_name; + size_t next_hashed_name_size; + Bitmap *types; + } nsec3; }; }; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index c25ac2216d..4bc4157028 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -28,6 +28,7 @@ #include "random-util.h" #include "hostname-util.h" #include "dns-domain.h" +#include "resolved-llmnr.h" #include "resolved-dns-scope.h" #define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) @@ -124,17 +125,17 @@ void dns_scope_next_dns_server(DnsScope *s) { manager_next_dns_server(s->manager); } -int dns_scope_emit(DnsScope *s, DnsPacket *p) { +int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) { union in_addr_union addr; int ifindex = 0, r; int family; uint16_t port; uint32_t mtu; - int fd; assert(s); assert(p); assert(p->protocol == s->protocol); + assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); if (s->link) { mtu = s->link->mtu; @@ -143,33 +144,18 @@ int dns_scope_emit(DnsScope *s, DnsPacket *p) { mtu = manager_find_mtu(s->manager); if (s->protocol == DNS_PROTOCOL_DNS) { - DnsServer *srv; - if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; - srv = dns_scope_get_dns_server(s); - if (!srv) - return -ESRCH; - - family = srv->family; - addr = srv->address; - port = 53; - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) return -EMSGSIZE; - if (p->size > mtu) + if (p->size + UDP_PACKET_HEADER_SIZE > mtu) return -EMSGSIZE; - if (family == AF_INET) - fd = manager_dns_ipv4_fd(s->manager); - else if (family == AF_INET6) - fd = manager_dns_ipv6_fd(s->manager); - else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; + r = manager_write(s->manager, fd, p); + if (r < 0) + return r; } else if (s->protocol == DNS_PROTOCOL_LLMNR) { @@ -180,7 +166,7 @@ int dns_scope_emit(DnsScope *s, DnsPacket *p) { return -EBUSY; family = s->family; - port = 5355; + port = LLMNR_PORT; if (family == AF_INET) { addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; @@ -192,17 +178,18 @@ int dns_scope_emit(DnsScope *s, DnsPacket *p) { return -EAFNOSUPPORT; if (fd < 0) return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, port, p); + if (r < 0) + return r; } else return -EAFNOSUPPORT; - r = manager_send(s->manager, fd, ifindex, family, &addr, port, p); - if (r < 0) - return r; - return 1; } -int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port) { +static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { + DnsServer *srv = NULL; _cleanup_close_ int fd = -1; union sockaddr_union sa = {}; socklen_t salen; @@ -213,8 +200,6 @@ int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *add assert((family == AF_UNSPEC) == !address); if (family == AF_UNSPEC) { - DnsServer *srv; - srv = dns_scope_get_dns_server(s); if (!srv) return -ESRCH; @@ -247,13 +232,15 @@ int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *add return -EAFNOSUPPORT; } - fd = socket(sa.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (fd < 0) return -errno; - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; + if (type == SOCK_STREAM) { + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + } if (s->link) { uint32_t ifindex = htobe32(s->link->ifindex); @@ -287,12 +274,23 @@ int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *add if (r < 0 && errno != EINPROGRESS) return -errno; + if (server) + *server = srv; + ret = fd; fd = -1; return ret; } +int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server) { + return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, 53, server); +} + +int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { + return dns_scope_socket(s, SOCK_STREAM, family, address, port, server); +} + DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { char **i; @@ -315,6 +313,11 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co if (is_localhost(domain)) return DNS_SCOPE_NO; + /* Never resolve any loopback IP address via DNS, LLMNR or mDNS */ + if (dns_name_endswith(domain, "127.in-addr.arpa") > 0 || + dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) + return DNS_SCOPE_NO; + if (s->protocol == DNS_PROTOCOL_DNS) { if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0 && @@ -415,19 +418,6 @@ int dns_scope_llmnr_membership(DnsScope *s, bool b) { return 0; } -int dns_scope_good_dns_server(DnsScope *s, int family, const union in_addr_union *address) { - assert(s); - assert(address); - - if (s->protocol != DNS_PROTOCOL_DNS) - return 1; - - if (s->link) - return !!link_find_dns_server(s->link, family, address); - else - return !!manager_find_dns_server(s->manager, family, address); -} - static int dns_scope_make_reply_packet( DnsScope *s, uint16_t id, @@ -546,7 +536,7 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { return; } - if (DNS_PACKET_C(p)) { + if (DNS_PACKET_LLMNR_C(p)) { /* Somebody notified us about a possible conflict */ dns_scope_verify_conflicts(s, p); return; @@ -695,7 +685,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata return 0; } - r = dns_scope_emit(scope, p); + r = dns_scope_emit(scope, -1, p); if (r < 0) log_debug_errno(r, "Failed to send conflict packet: %m"); } @@ -760,10 +750,10 @@ void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { if (DNS_PACKET_RRCOUNT(p) <= 0) return; - if (DNS_PACKET_C(p) != 0) + if (DNS_PACKET_LLMNR_C(p) != 0) return; - if (DNS_PACKET_T(p) != 0) + if (DNS_PACKET_LLMNR_T(p) != 0) return; if (manager_our_packet(scope->manager, p)) diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index cfbde1343f..29479ad550 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -65,12 +65,12 @@ struct DnsScope { int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); DnsScope* dns_scope_free(DnsScope *s); -int dns_scope_emit(DnsScope *s, DnsPacket *p); -int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port); +int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p); +int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server); +int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server); DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); int dns_scope_good_key(DnsScope *s, DnsResourceKey *key); -int dns_scope_good_dns_server(DnsScope *s, int family, const union in_addr_union *address); DnsServer *dns_scope_get_dns_server(DnsScope *s); void dns_scope_next_dns_server(DnsScope *s); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 9a62a63258..92e48ae442 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -41,6 +41,7 @@ int dns_server_new( if (!s) return -ENOMEM; + s->n_ref = 1; s->type = type; s->family = family; s->address = *in_addr; @@ -74,33 +75,46 @@ int dns_server_new( return 0; } -DnsServer* dns_server_free(DnsServer *s) { +DnsServer* dns_server_ref(DnsServer *s) { if (!s) return NULL; - if (s->link) { - if (s->type == DNS_SERVER_LINK) - LIST_REMOVE(servers, s->link->dns_servers, s); + assert(s->n_ref > 0); - if (s->link->current_dns_server == s) - link_set_dns_server(s->link, NULL); - } + s->n_ref ++; - if (s->manager) { - if (s->type == DNS_SERVER_SYSTEM) - LIST_REMOVE(servers, s->manager->dns_servers, s); - else if (s->type == DNS_SERVER_FALLBACK) - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + return s; +} + +static DnsServer* dns_server_free(DnsServer *s) { + if (!s) + return NULL; - if (s->manager->current_dns_server == s) - manager_set_dns_server(s->manager, NULL); - } + if (s->link && s->link->current_dns_server == s) + link_set_dns_server(s->link, NULL); + + if (s->manager && s->manager->current_dns_server == s) + manager_set_dns_server(s->manager, NULL); free(s); return NULL; } +DnsServer* dns_server_unref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + + if (s->n_ref == 1) + dns_server_free(s); + else + s->n_ref --; + + return NULL; +} + static unsigned long dns_server_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) { const DnsServer *s = p; uint64_t u; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 70ff35b08f..06059e8829 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -37,6 +37,8 @@ typedef enum DnsServerType { struct DnsServer { Manager *manager; + unsigned n_ref; + DnsServerType type; Link *link; @@ -57,6 +59,9 @@ int dns_server_new( int family, const union in_addr_union *address); -DnsServer* dns_server_free(DnsServer *s); +DnsServer* dns_server_ref(DnsServer *s); +DnsServer* dns_server_unref(DnsServer *s); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 214938986d..8a93b265c6 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -21,6 +21,7 @@ #include "af-list.h" +#include "resolved-llmnr.h" #include "resolved-dns-transaction.h" #include "random-util.h" @@ -38,6 +39,10 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_packet_unref(t->received); dns_answer_unref(t->cached); + sd_event_source_unref(t->dns_event_source); + safe_close(t->dns_fd); + + dns_server_unref(t->server); dns_stream_free(t->stream); if (t->scope) { @@ -87,6 +92,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsQuestion *q) { if (!t) return -ENOMEM; + t->dns_fd = -1; + t->question = dns_question_ref(q); do @@ -236,6 +243,7 @@ static int on_stream_complete(DnsStream *s, int error) { } static int dns_transaction_open_tcp(DnsTransaction *t) { + DnsServer *server = NULL; _cleanup_close_ int fd = -1; int r; @@ -245,12 +253,12 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return 0; if (t->scope->protocol == DNS_PROTOCOL_DNS) - fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53); + fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server); else if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { /* When we already received a query to this (but it was truncated), send to its sender address */ if (t->received) - fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port); + fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL); else { union in_addr_union address; int family = AF_UNSPEC; @@ -264,7 +272,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (r == 0) return -EINVAL; - fd = dns_scope_tcp_socket(t->scope, family, &address, 5355); + fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL); } } else return -EAFNOSUPPORT; @@ -284,6 +292,9 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return r; } + + dns_server_unref(t->server); + t->server = dns_server_ref(server); t->received = dns_packet_unref(t->received); t->stream->complete = on_stream_complete; t->stream->transaction = t; @@ -297,6 +308,16 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return 0; } +static void dns_transaction_next_dns_server(DnsTransaction *t) { + assert(t); + + t->server = dns_server_unref(t->server); + t->dns_event_source = sd_event_source_unref(t->dns_event_source); + t->dns_fd = safe_close(t->dns_fd); + + dns_scope_next_dns_server(t->scope); +} + void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { int r; @@ -323,25 +344,12 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Tentative packets are not full responses but still * useful for identifying uniqueness conflicts during * probing. */ - if (DNS_PACKET_T(p)) { + if (DNS_PACKET_LLMNR_T(p)) { dns_transaction_tentative(t, p); return; } } - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - - /* For DNS we are fine with accepting packets on any - * interface, but the source IP address must be one of - * a valid DNS server */ - - if (!dns_scope_good_dns_server(t->scope, p->family, &p->sender)) - return; - - if (p->sender_port != 53) - return; - } - if (t->received != p) { dns_packet_unref(t->received); t->received = dns_packet_ref(p); @@ -378,7 +386,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_scope_next_dns_server(t->scope); + dns_transaction_next_dns_server(t); r = dns_transaction_go(t); if (r < 0) { @@ -397,6 +405,13 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } + /* Only consider responses with equivalent query section to the request */ + r = dns_question_is_equal(p->question, t->question); + if (r <= 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ dns_cache_put(&t->scope->cache, p->question, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender); @@ -406,6 +421,56 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); } +static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = userdata; + int r; + + assert(t); + assert(t->scope); + + r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); + if (r <= 0) + return r; + + if (dns_packet_validate_reply(p) > 0 && + DNS_PACKET_ID(p) == t->id) { + dns_transaction_process_reply(t, p); + } else + log_debug("Invalid DNS packet."); + + return 0; +} + +static int dns_transaction_emit(DnsTransaction *t) { + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) { + DnsServer *server = NULL; + _cleanup_close_ int fd = -1; + + fd = dns_scope_udp_dns_socket(t->scope, &server); + if (fd < 0) + return fd; + + r = sd_event_add_io(t->scope->manager->event, &t->dns_event_source, fd, EPOLLIN, on_dns_packet, t); + if (r < 0) + return r; + + t->dns_fd = fd; + fd = -1; + t->server = dns_server_ref(server); + } + + r = dns_scope_emit(t->scope, t->dns_fd, t->sent); + if (r < 0) + return r; + + return 0; +} + static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { DnsTransaction *t = userdata; int r; @@ -414,7 +479,7 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat assert(t); /* Timeout reached? Try again, with a new server */ - dns_scope_next_dns_server(t->scope); + dns_transaction_next_dns_server(t); r = dns_transaction_go(t); if (r < 0) @@ -571,7 +636,7 @@ int dns_transaction_go(DnsTransaction *t) { r = dns_transaction_open_tcp(t); } else { /* Try via UDP, and if that fails due to large size try via TCP */ - r = dns_scope_emit(t->scope, t->sent); + r = dns_transaction_emit(t); if (r == -EMSGSIZE) r = dns_transaction_open_tcp(t); } @@ -579,15 +644,14 @@ int dns_transaction_go(DnsTransaction *t) { /* No servers to send this to? */ dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); return 0; - } - if (r < 0) { + } else if (r < 0) { if (t->scope->protocol != DNS_PROTOCOL_DNS) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return 0; } /* Couldn't send? Try immediately again, with a new server */ - dns_scope_next_dns_server(t->scope); + dns_transaction_next_dns_server(t); return dns_transaction_go(t); } diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index f6d539d315..a8f4267bc8 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -61,6 +61,12 @@ struct DnsTransaction { sd_event_source *timeout_event_source; unsigned n_attempts; + int dns_fd; + sd_event_source *dns_event_source; + + /* the active server */ + DnsServer *server; + /* TCP connection logic, if we need it */ DnsStream *stream; diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index ff8dc3a5bc..d66b3a88fc 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -58,7 +58,6 @@ int link_new(Manager *m, Link **ret, int ifindex) { } Link *link_free(Link *l) { - if (!l) return NULL; @@ -68,8 +67,12 @@ Link *link_free(Link *l) { if (l->manager) hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); - while (l->dns_servers) - dns_server_free(l->dns_servers); + while (l->dns_servers) { + DnsServer *s = l->dns_servers; + + LIST_REMOVE(servers, l->dns_servers, s); + dns_server_unref(s); + } dns_scope_free(l->unicast_scope); dns_scope_free(l->llmnr_ipv4_scope); @@ -182,14 +185,20 @@ static int link_update_dns_servers(Link *l) { } LIST_FOREACH_SAFE(servers, s, nx, l->dns_servers) - if (s->marked) - dns_server_free(s); + if (s->marked) { + LIST_REMOVE(servers, l->dns_servers, s); + dns_server_unref(s); + } return 0; clear: - while (l->dns_servers) - dns_server_free(l->dns_servers); + while (l->dns_servers) { + s = l->dns_servers; + + LIST_REMOVE(servers, l->dns_servers, s); + dns_server_unref(s); + } return r; } diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c new file mode 100644 index 0000000000..8afaf8db6e --- /dev/null +++ b/src/resolve/resolved-llmnr.c @@ -0,0 +1,473 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen <teg@jklm.no> + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include <resolv.h> +#include <netinet/in.h> + +#include "resolved-manager.h" +#include "resolved-llmnr.h" + +void manager_llmnr_stop(Manager *m) { + assert(m); + + m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + + m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + + m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + + m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); +} + +int manager_llmnr_start(Manager *m) { + int r; + + assert(m); + + if (m->llmnr_support == SUPPORT_NO) + return 0; + + r = manager_llmnr_ipv4_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv4_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_llmnr_ipv6_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv6_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); + m->llmnr_support = SUPPORT_NO; + manager_llmnr_stop(m); + + return 0; +} + +static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_check_conflicts(scope, p); + + t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); + if (t) + dns_transaction_process_reply(t, p); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid LLMNR UDP packet."); + + return 0; +} + +int manager_llmnr_ipv4_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv4_udp_fd >= 0) + return m->llmnr_ipv4_udp_fd; + + m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_udp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); + if (r < 0) + goto fail; + + return m->llmnr_ipv4_udp_fd; + +fail: + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + return r; +} + +int manager_llmnr_ipv6_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv6_udp_fd >= 0) + return m->llmnr_ipv6_udp_fd; + + m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_udp_fd < 0) + return -errno; + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); + if (r < 0) { + r = -errno; + goto fail; + } + + return m->llmnr_ipv6_udp_fd; + +fail: + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + return r; +} + +static int on_llmnr_stream_packet(DnsStream *s) { + DnsScope *scope; + + assert(s); + + scope = manager_find_scope(s->manager, s->read_packet); + if (!scope) { + log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); + return 0; + } + + if (dns_packet_validate_query(s->read_packet) > 0) { + log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); + + dns_scope_process_query(scope, s, s->read_packet); + + /* If no reply packet was set, we free the stream */ + if (s->write_packet) + return 0; + } else + log_debug("Invalid LLMNR TCP packet."); + + dns_stream_free(s); + return 0; +} + +static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + DnsStream *stream; + Manager *m = userdata; + int cfd, r; + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (cfd < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); + if (r < 0) { + safe_close(cfd); + return r; + } + + stream->on_packet = on_llmnr_stream_packet; + return 0; +} + +int manager_llmnr_ipv4_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT; + int r; + + assert(m); + + if (m->llmnr_ipv4_tcp_fd >= 0) + return m->llmnr_ipv4_tcp_fd; + + m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); + if (r < 0) + goto fail; + + return m->llmnr_ipv4_tcp_fd; + +fail: + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + return r; +} + +int manager_llmnr_ipv6_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1; + int r; + + assert(m); + + if (m->llmnr_ipv6_tcp_fd >= 0) + return m->llmnr_ipv6_tcp_fd; + + m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); + if (r < 0) { + r = -errno; + goto fail; + } + + return m->llmnr_ipv6_tcp_fd; + +fail: + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); + return r; +} diff --git a/src/resolve/resolved-llmnr.h b/src/resolve/resolved-llmnr.h new file mode 100644 index 0000000000..d489d481e8 --- /dev/null +++ b/src/resolve/resolved-llmnr.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-manager.h" + +#define LLMNR_PORT 5355 + +int manager_llmnr_ipv4_udp_fd(Manager *m); +int manager_llmnr_ipv6_udp_fd(Manager *m); +int manager_llmnr_ipv4_tcp_fd(Manager *m); +int manager_llmnr_ipv6_tcp_fd(Manager *m); + +void manager_llmnr_stop(Manager *m); +int manager_llmnr_start(Manager *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index dee5e61922..5be01d3cb8 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -38,6 +38,7 @@ #include "resolved-conf.h" #include "resolved-bus.h" #include "resolved-manager.h" +#include "resolved-llmnr.h" #define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) @@ -393,66 +394,6 @@ static int manager_watch_hostname(Manager *m) { return 0; } -static void manager_llmnr_stop(Manager *m) { - assert(m); - - m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - - m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - - m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - - m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); -} - -static int manager_llmnr_start(Manager *m) { - int r; - - assert(m); - - if (m->llmnr_support == SUPPORT_NO) - return 0; - - r = manager_llmnr_ipv4_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv4_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_llmnr_ipv6_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv6_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = SUPPORT_NO; - manager_llmnr_stop(m); - - return 0; -} - int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; @@ -463,7 +404,6 @@ int manager_new(Manager **ret) { if (!m) return -ENOMEM; - m->dns_ipv4_fd = m->dns_ipv6_fd = -1; m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; m->hostname_fd = -1; @@ -545,11 +485,6 @@ Manager *manager_free(Manager *m) { sd_event_source_unref(m->network_event_source); sd_network_monitor_unref(m->network_monitor); - sd_event_source_unref(m->dns_ipv4_event_source); - sd_event_source_unref(m->dns_ipv6_event_source); - safe_close(m->dns_ipv4_fd); - safe_close(m->dns_ipv6_fd); - manager_llmnr_stop(m); sd_bus_slot_unref(m->prepare_for_sleep_slot); @@ -662,8 +597,10 @@ int manager_read_resolv_conf(Manager *m) { } LIST_FOREACH_SAFE(servers, s, nx, m->dns_servers) - if (s->marked) - dns_server_free(s); + if (s->marked) { + LIST_REMOVE(servers, m->dns_servers, s); + dns_server_unref(s); + } /* Whenever /etc/resolv.conf changes, start using the first * DNS server of it. This is useful to deal with broken @@ -678,8 +615,12 @@ int manager_read_resolv_conf(Manager *m) { return 0; clear: - while (m->dns_servers) - dns_server_free(m->dns_servers); + while (m->dns_servers) { + s = m->dns_servers; + + LIST_REMOVE(servers, m->dns_servers, s); + dns_server_unref(s); + } return r; } @@ -971,10 +912,12 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { if (p->ifindex == LOOPBACK_IFINDEX) p->ifindex = 0; - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (p->ifindex <= 0) - p->ifindex = manager_find_ifindex(m, p->family, &p->destination); + if (protocol != DNS_PROTOCOL_DNS) { + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (p->ifindex <= 0) + p->ifindex = manager_find_ifindex(m, p->family, &p->destination); + } *ret = p; p = NULL; @@ -982,97 +925,38 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { return 1; } -static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = NULL; - Manager *m = userdata; +static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { int r; - r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p); - if (r <= 0) - return r; + assert(fd >= 0); + assert(mh); - if (dns_packet_validate_reply(p) > 0) { - t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); - if (!t) + for (;;) { + if (sendmsg(fd, mh, flags) >= 0) return 0; - dns_transaction_process_reply(t, p); - - } else - log_debug("Invalid DNS packet."); - - return 0; -} - -int manager_dns_ipv4_fd(Manager *m) { - const int one = 1; - int r; - - assert(m); - - if (m->dns_ipv4_fd >= 0) - return m->dns_ipv4_fd; - - m->dns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->dns_ipv4_fd < 0) - return -errno; - - r = setsockopt(m->dns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->dns_ipv4_event_source, m->dns_ipv4_fd, EPOLLIN, on_dns_packet, m); - if (r < 0) - goto fail; - - return m->dns_ipv4_fd; - -fail: - m->dns_ipv4_fd = safe_close(m->dns_ipv4_fd); - return r; -} - -int manager_dns_ipv6_fd(Manager *m) { - const int one = 1; - int r; - - assert(m); - - if (m->dns_ipv6_fd >= 0) - return m->dns_ipv6_fd; + if (errno == EINTR) + continue; - m->dns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->dns_ipv6_fd < 0) - return -errno; + if (errno != EAGAIN) + return -errno; - r = setsockopt(m->dns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; } - - r = sd_event_add_io(m->event, &m->dns_ipv6_event_source, m->dns_ipv6_fd, EPOLLIN, on_dns_packet, m); - if (r < 0) - goto fail; - - return m->dns_ipv6_fd; - -fail: - m->dns_ipv6_fd = safe_close(m->dns_ipv6_fd); - return r; } -static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { +static int write_loop(int fd, void *message, size_t length) { int r; assert(fd >= 0); - assert(mh); + assert(message); for (;;) { - if (sendmsg(fd, mh, flags) >= 0) + if (write(fd, message, length) >= 0) return 0; if (errno == EINTR) @@ -1089,6 +973,18 @@ static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { } } +int manager_write(Manager *m, int fd, DnsPacket *p) { + int r; + + log_debug("Sending %s packet with id %u", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); + + r = write_loop(fd, DNS_PACKET_DATA(p), p->size); + if (r < 0) + return r; + + return 0; +} + static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { union sockaddr_union sa = { .in.sin_family = AF_INET, @@ -1316,393 +1212,6 @@ uint32_t manager_find_mtu(Manager *m) { return mtu; } -static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - log_debug("Got reply packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_check_conflicts(scope, p); - - t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); - if (t) - dns_transaction_process_reply(t, p); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid LLMNR UDP packet."); - - return 0; -} - -int manager_llmnr_ipv4_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(5355), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv4_udp_fd >= 0) - return m->llmnr_ipv4_udp_fd; - - m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_udp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - return m->llmnr_ipv4_udp_fd; - -fail: - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - return r; -} - -int manager_llmnr_ipv6_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(5355), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv6_udp_fd >= 0) - return m->llmnr_ipv6_udp_fd; - - m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_udp_fd < 0) - return -errno; - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) { - r = -errno; - goto fail; - } - - return m->llmnr_ipv6_udp_fd; - -fail: - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - return r; -} - -static int on_llmnr_stream_packet(DnsStream *s) { - DnsScope *scope; - - assert(s); - - scope = manager_find_scope(s->manager, s->read_packet); - if (!scope) { - log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); - return 0; - } - - if (dns_packet_validate_query(s->read_packet) > 0) { - log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); - - dns_scope_process_query(scope, s, s->read_packet); - - /* If no reply packet was set, we free the stream */ - if (s->write_packet) - return 0; - } else - log_debug("Invalid LLMNR TCP packet."); - - dns_stream_free(s); - return 0; -} - -static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - DnsStream *stream; - Manager *m = userdata; - int cfd, r; - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (cfd < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); - if (r < 0) { - safe_close(cfd); - return r; - } - - stream->on_packet = on_llmnr_stream_packet; - return 0; -} - -int manager_llmnr_ipv4_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(5355), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT; - int r; - - assert(m); - - if (m->llmnr_ipv4_tcp_fd >= 0) - return m->llmnr_ipv4_tcp_fd; - - m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - return m->llmnr_ipv4_tcp_fd; - -fail: - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - return r; -} - -int manager_llmnr_ipv6_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(5355), - }; - static const int one = 1; - int r; - - assert(m); - - if (m->llmnr_ipv6_tcp_fd >= 0) - return m->llmnr_ipv6_tcp_fd; - - m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) { - r = -errno; - goto fail; - } - - return m->llmnr_ipv6_tcp_fd; - -fail: - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); - return r; -} - int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { LinkAddress *a; @@ -1827,15 +1336,25 @@ void manager_verify_all(Manager *m) { } void manager_flush_dns_servers(Manager *m, DnsServerType t) { + DnsServer *s; + assert(m); if (t == DNS_SERVER_SYSTEM) - while (m->dns_servers) - dns_server_free(m->dns_servers); + while (m->dns_servers) { + s = m->dns_servers; + + LIST_REMOVE(servers, m->dns_servers, s); + dns_server_unref(s); + } if (t == DNS_SERVER_FALLBACK) - while (m->fallback_dns_servers) - dns_server_free(m->fallback_dns_servers); + while (m->fallback_dns_servers) { + s = m->fallback_dns_servers; + + LIST_REMOVE(servers, m->fallback_dns_servers, s); + dns_server_unref(s); + } } static const char* const support_table[_SUPPORT_MAX] = { diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 0f4ffad141..53b5acb33c 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -65,12 +65,6 @@ struct Manager { unsigned n_dns_streams; /* Unicast dns */ - int dns_ipv4_fd; - int dns_ipv6_fd; - - sd_event_source *dns_ipv4_event_source; - sd_event_source *dns_ipv6_event_source; - LIST_HEAD(DnsServer, dns_servers); LIST_HEAD(DnsServer, fallback_dns_servers); DnsServer *current_dns_server; @@ -125,16 +119,10 @@ void manager_next_dns_server(Manager *m); uint32_t manager_find_mtu(Manager *m); +int manager_write(Manager *m, int fd, DnsPacket *p); int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); -int manager_dns_ipv4_fd(Manager *m); -int manager_dns_ipv6_fd(Manager *m); -int manager_llmnr_ipv4_udp_fd(Manager *m); -int manager_llmnr_ipv6_udp_fd(Manager *m); -int manager_llmnr_ipv4_tcp_fd(Manager *m); -int manager_llmnr_ipv6_tcp_fd(Manager *m); - int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); diff --git a/src/rfkill/rfkill.c b/src/rfkill/rfkill.c index 5a90c778fb..904dec6bfc 100644 --- a/src/rfkill/rfkill.c +++ b/src/rfkill/rfkill.c @@ -127,7 +127,7 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - r = write_string_file(saved, value); + r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE); if (r < 0) { log_error_errno(r, "Failed to write %s: %m", saved); return EXIT_FAILURE; diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 20a44ce4e1..8a0dec1540 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -114,6 +114,68 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { return r; } +/* @label_terminal: terminal character of a label, updated to point to the terminal character of + * the previous label (always skipping one dot) or to NULL if there are no more + * labels. */ +int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { + const char *terminal; + int r; + + assert(name); + assert(label_terminal); + assert(dest); + + /* no more labels */ + if (!*label_terminal) { + if (sz >= 1) + *dest = 0; + + return 0; + } + + assert(**label_terminal == '.' || **label_terminal == 0); + + /* skip current terminal character */ + terminal = *label_terminal - 1; + + /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + for (;;) { + if (terminal < name) { + /* reached the first label, so indicate that there are no more */ + terminal = NULL; + break; + } + + /* find the start of the last label */ + if (*terminal == '.') { + const char *y; + unsigned slashes = 0; + + for (y = terminal - 1; y >= name && *y == '\\'; y--) + slashes ++; + + if (slashes % 2 == 0) { + /* the '.' was not escaped */ + name = terminal + 1; + break; + } else { + terminal = y; + continue; + } + } + + terminal --; + } + + r = dns_label_unescape(&name, dest, sz); + if (r < 0) + return r; + + *label_terminal = terminal; + + return r; +} + int dns_label_escape(const char *p, size_t l, char **ret) { _cleanup_free_ char *s = NULL; char *q; @@ -338,20 +400,23 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_ } int dns_name_compare_func(const void *a, const void *b) { - const char *x = a, *y = b; + const char *x, *y; int r, q, k, w; assert(a); assert(b); + x = (const char *) a + strlen(a); + y = (const char *) b + strlen(b); + for (;;) { char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; - if (*x == 0 && *y == 0) + if (x == NULL && y == NULL) return 0; - r = dns_label_unescape(&x, la, sizeof(la)); - q = dns_label_unescape(&y, lb, sizeof(lb)); + r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); + q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); if (r < 0 || q < 0) return r - q; @@ -464,6 +529,28 @@ int dns_name_endswith(const char *name, const char *suffix) { } } +int dns_name_between(const char *a, const char *b, const char *c) { + int n; + + /* Determine if b is strictly greater than a and strictly smaller than c. + We consider the order of names to be circular, so that if a is + strictly greater than c, we consider b to be between them if it is + either greater than a or smaller than c. This is how the canonical + DNS name order used in NSEC records work. */ + + n = dns_name_compare_func(a, c); + if (n == 0) + return -EINVAL; + else if (n < 0) + /* a<---b--->c */ + return dns_name_compare_func(a, b) < 0 && + dns_name_compare_func(b, c) < 0; + else + /* <--b--c a--b--> */ + return dns_name_compare_func(b, c) < 0 || + dns_name_compare_func(a, b) < 0; +} + int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { const uint8_t *p; int r; diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 00caf5d700..bd50ad3e6d 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -29,6 +29,7 @@ #define DNS_NAME_MAX 255 int dns_label_unescape(const char **name, char *dest, size_t sz); +int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); int dns_label_escape(const char *p, size_t l, char **ret); int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); @@ -49,6 +50,7 @@ unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_ int dns_name_compare_func(const void *a, const void *b); extern const struct hash_ops dns_name_hash_ops; +int dns_name_between(const char *a, const char *b, const char *c); int dns_name_equal(const char *x, const char *y); int dns_name_endswith(const char *name, const char *suffix); diff --git a/src/shared/efivars.c b/src/shared/efivars.c index 0d6ecf52cf..347cd30b09 100644 --- a/src/shared/efivars.c +++ b/src/shared/efivars.c @@ -125,7 +125,19 @@ static int get_os_indications(uint64_t *os_indication) { return r; r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s); - if (r < 0) + if (r == -ENOENT) { + /* Some firmware implementations that do support + * OsIndications and report that with + * OsIndicationsSupported will remove the + * OsIndications variable when it is unset. Let's + * pretend it's 0 then, to hide this implementation + * detail. Note that this call will return -ENOENT + * then only if the support for OsIndications is + * missing entirely, as determined by + * efi_reboot_to_firmware_supported() above. */ + *os_indication = 0; + return 0; + } else if (r < 0) return r; else if (s != sizeof(uint64_t)) return -EINVAL; diff --git a/src/shared/install.c b/src/shared/install.c index c37cf1948a..3d2b5ae77f 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -2190,6 +2190,7 @@ int unit_file_get_list( _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL; struct dirent *de; _cleanup_free_ char *path = NULL; + bool also = false; errno = 0; de = readdir(d); @@ -2243,7 +2244,7 @@ int unit_file_get_list( if (!path) return -ENOMEM; - r = unit_file_can_install(&paths, root_dir, path, true, NULL); + r = unit_file_can_install(&paths, root_dir, path, true, &also); if (r == -EINVAL || /* Invalid setting? */ r == -EBADMSG || /* Invalid format? */ r == -ENOENT /* Included file not found? */) @@ -2253,7 +2254,7 @@ int unit_file_get_list( else if (r > 0) f->state = UNIT_FILE_DISABLED; else - f->state = UNIT_FILE_STATIC; + f->state = also ? UNIT_FILE_INDIRECT : UNIT_FILE_STATIC; found: r = hashmap_put(h, basename(f->path), f); diff --git a/src/shared/nss-util.h b/src/shared/nss-util.h index 230a986040..3657aa5d9c 100644 --- a/src/shared/nss-util.h +++ b/src/shared/nss-util.h @@ -24,6 +24,9 @@ #include <nss.h> #include <netdb.h> #include <resolv.h> +#include <pwd.h> +#include <grp.h> + #define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ enum nss_status _nss_##module##_gethostbyname4_r( \ @@ -109,7 +112,8 @@ enum nss_status _nss_##module##_gethostbyname_r( \ NULL, \ NULL); \ return ret; \ -} +} \ +struct __useless_struct_to_allow_trailing_semicolon__ #define NSS_GETHOSTBYADDR_FALLBACKS(module) \ enum nss_status _nss_##module##_gethostbyaddr_r( \ @@ -125,4 +129,29 @@ enum nss_status _nss_##module##_gethostbyaddr_r( \ buffer, buflen, \ errnop, h_errnop, \ NULL); \ -} +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define NSS_GETPW_PROTOTYPES(module) \ +enum nss_status _nss_##module##_getpwnam_r( \ + const char *name, \ + struct passwd *pwd, \ + char *buffer, size_t buflen, \ + int *errnop) _public_; \ +enum nss_status _nss_mymachines_getpwuid_r( \ + uid_t uid, \ + struct passwd *pwd, \ + char *buffer, size_t buflen, \ + int *errnop) _public_ + +#define NSS_GETGR_PROTOTYPES(module) \ +enum nss_status _nss_##module##_getgrnam_r( \ + const char *name, \ + struct group *gr, \ + char *buffer, size_t buflen, \ + int *errnop) _public_; \ +enum nss_status _nss_##module##_getgrgid_r( \ + gid_t gid, \ + struct group *gr, \ + char *buffer, size_t buflen, \ + int *errnop) _public_ diff --git a/src/shared/sysctl-util.c b/src/shared/sysctl-util.c index 55f4e48601..1de0b94fd5 100644 --- a/src/shared/sysctl-util.c +++ b/src/shared/sysctl-util.c @@ -66,7 +66,7 @@ int sysctl_write(const char *property, const char *value) { log_debug("Setting '%s' to '%s'", property, value); p = strjoina("/proc/sys/", property); - return write_string_file(p, value); + return write_string_file(p, value, 0); } int sysctl_read(const char *property, char **content) { diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index eee6bc8982..2b2310152d 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -42,7 +42,7 @@ static int write_mode(char **modes) { STRV_FOREACH(mode, modes) { int k; - k = write_string_file("/sys/power/disk", *mode); + k = write_string_file("/sys/power/disk", *mode, 0); if (k == 0) return 0; @@ -65,7 +65,7 @@ static int write_state(FILE **f, char **states) { STRV_FOREACH(state, states) { int k; - k = write_string_stream(*f, *state); + k = write_string_stream(*f, *state, true); if (k == 0) return 0; log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h index a03221502a..bb4b1eb748 100644 --- a/src/systemd/sd-bus-vtable.h +++ b/src/systemd/sd-bus-vtable.h @@ -67,6 +67,7 @@ struct sd_bus_vtable { const char *signature; const char *result; sd_bus_message_handler_t handler; + size_t offset; } method; struct { const char *member; @@ -89,7 +90,7 @@ struct sd_bus_vtable { .x.start.element_size = sizeof(sd_bus_vtable), \ } -#define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ +#define SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, _offset, _flags) \ { \ .type = _SD_BUS_VTABLE_METHOD, \ .flags = _flags, \ @@ -97,7 +98,10 @@ struct sd_bus_vtable { .x.method.signature = _signature, \ .x.method.result = _result, \ .x.method.handler = _handler, \ + .x.method.offset = _offset, \ } +#define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ + SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, 0, _flags) #define SD_BUS_SIGNAL(_member, _signature, _flags) \ { \ diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h index f34893171f..5439a1903b 100644 --- a/src/systemd/sd-bus.h +++ b/src/systemd/sd-bus.h @@ -205,7 +205,7 @@ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot); void *sd_bus_slot_get_userdata(sd_bus_slot *slot); void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata); int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description); -int sd_bus_slot_get_description(sd_bus_slot *slot, char **description); +int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description); sd_bus_message* sd_bus_slot_get_current_message(sd_bus_slot *slot); sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *bus); diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 4296b91d8a..5afa50a9d0 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -45,6 +45,8 @@ int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routesgn); +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const uint8_t **data, + size_t *data_len); int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const uint8_t **client_id, size_t *client_id_len); diff --git a/src/test/test-bitmap.c b/src/test/test-bitmap.c new file mode 100644 index 0000000000..96deeded7e --- /dev/null +++ b/src/test/test-bitmap.c @@ -0,0 +1,105 @@ +/*** + This file is part of systemd + + Copyright 2015 Tom Gundersen + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "bitmap.h" + +int main(int argc, const char *argv[]) { + _cleanup_bitmap_free_ Bitmap *b = NULL; + Iterator it; + unsigned n = (unsigned) -1, i = 0; + + b = bitmap_new(); + assert_se(b); + + assert_se(bitmap_ensure_allocated(&b) == 0); + bitmap_free(b); + b = NULL; + assert_se(bitmap_ensure_allocated(&b) == 0); + + assert_se(bitmap_isset(b, 0) == false); + assert_se(bitmap_isset(b, 1) == false); + assert_se(bitmap_isset(b, 256) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 0) == 0); + assert_se(bitmap_isset(b, 0) == true); + assert_se(bitmap_isclear(b) == false); + bitmap_unset(b, 0); + assert_se(bitmap_isset(b, 0) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 1) == 0); + assert_se(bitmap_isset(b, 1) == true); + assert_se(bitmap_isclear(b) == false); + bitmap_unset(b, 1); + assert_se(bitmap_isset(b, 1) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 256) == 0); + assert_se(bitmap_isset(b, 256) == true); + assert_se(bitmap_isclear(b) == false); + bitmap_unset(b, 256); + assert_se(bitmap_isset(b, 256) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 32) == 0); + bitmap_unset(b, 0); + assert_se(bitmap_isset(b, 32) == true); + bitmap_unset(b, 32); + + BITMAP_FOREACH(n, NULL, it) + assert_not_reached("NULL bitmap"); + + assert_se(bitmap_set(b, 0) == 0); + assert_se(bitmap_set(b, 1) == 0); + assert_se(bitmap_set(b, 256) == 0); + + BITMAP_FOREACH(n, b, it) { + assert_se(n == i); + if (i == 0) + i = 1; + else if (i == 1) + i = 256; + else if (i == 256) + i = (unsigned) -1; + } + + assert_se(i == (unsigned) -1); + + i = 0; + + BITMAP_FOREACH(n, b, it) { + assert_se(n == i); + if (i == 0) + i = 1; + else if (i == 1) + i = 256; + else if (i == 256) + i = (unsigned) -1; + } + + assert_se(i == (unsigned) -1); + + bitmap_clear(b); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, (unsigned) -1) == -ERANGE); + + return 0; +} diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index 838ffcba3d..e4771c9dd7 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); - r = write_string_file("/xxxtest/afile", "ljsadhfljasdkfhlkjdsfha"); + r = write_string_file("/xxxtest/afile", "ljsadhfljasdkfhlkjdsfha", WRITE_STRING_FILE_CREATE); if (r < 0) log_error_errno(r, "Failed to write file: %m"); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index b1385b8b87..b73c958ec5 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -43,7 +43,7 @@ static void test_copy_file(void) { assert_se(fd >= 0); close(fd); - assert_se(write_string_file(fn, "foo bar bar bar foo") == 0); + assert_se(write_string_file(fn, "foo bar bar bar foo", WRITE_STRING_FILE_CREATE) == 0); assert_se(copy_file(fn, fn_copy, 0, 0644, 0) == 0); @@ -67,7 +67,7 @@ static void test_copy_file_fd(void) { out_fd = mkostemp_safe(out_fn, O_RDWR); assert_se(out_fd >= 0); - assert_se(write_string_file(in_fn, text) == 0); + assert_se(write_string_file(in_fn, text, WRITE_STRING_FILE_CREATE) == 0); assert_se(copy_file_fd("/a/file/which/does/not/exist/i/guess", out_fd, true) < 0); assert_se(copy_file_fd(in_fn, out_fd, true) >= 0); assert_se(lseek(out_fd, SEEK_SET, 0) == 0); @@ -94,7 +94,7 @@ static void test_copy_tree(void) { char *f = strjoina(original_dir, *p); assert_se(mkdir_parents(f, 0755) >= 0); - assert_se(write_string_file(f, "file") == 0); + assert_se(write_string_file(f, "file", WRITE_STRING_FILE_CREATE) == 0); } STRV_FOREACH_PAIR(link, p, links) { diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 527cdd3b54..0042722c99 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -50,6 +50,46 @@ static void test_dns_label_unescape(void) { test_dns_label_unescape_one("foobar.", "foobar", 20, 6); } +static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) { + char buffer[buffer_sz]; + const char *label; + int r; + + label = what + strlen(what); + + r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); + assert_se(r == ret1); + if (r >= 0) + assert_se(streq(buffer, expect1)); + + r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); + assert_se(r == ret2); + if (r >= 0) + assert_se(streq(buffer, expect2)); +} + +static void test_dns_label_unescape_suffix(void) { + test_dns_label_unescape_suffix_one("hallo", "hallo", "", 6, 5, 0); + test_dns_label_unescape_suffix_one("hallo", "hallo", "", 4, -ENOSPC, -ENOSPC); + test_dns_label_unescape_suffix_one("", "", "", 10, 0, 0); + test_dns_label_unescape_suffix_one("hallo\\.foobar", "hallo.foobar", "", 20, 12, 0); + test_dns_label_unescape_suffix_one("hallo.foobar", "foobar", "hallo", 10, 6, 5); + test_dns_label_unescape_suffix_one("hallo.foobar\n", "foobar", "foobar", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0); + test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one("..", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL); + test_dns_label_unescape_suffix_one("foobar.", "", "foobar", 20, 0, 6); + test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0); + test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3); + test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL); + test_dns_label_unescape_suffix_one("foo...bar", "bar", "", 20, 3, -EINVAL); + test_dns_label_unescape_suffix_one("foo\\.bar", "foo.bar", "", 20, 7, 0); + test_dns_label_unescape_suffix_one("foo\\\\.bar", "bar", "foo\\", 20, 3, 4); + test_dns_label_unescape_suffix_one("foo\\\\\\.bar", "foo\\.bar", "", 20, 8, 0); +} + static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { _cleanup_free_ char *t = NULL; int r; @@ -120,6 +160,38 @@ static void test_dns_name_equal(void) { test_dns_name_equal_one("..", "..", -EINVAL); } +static void test_dns_name_between_one(const char *a, const char *b, const char *c, int ret) { + int r; + + r = dns_name_between(a, b, c); + assert_se(r == ret); + + r = dns_name_between(c, b, a); + if (ret >= 0) + assert_se(r == 0); + else + assert_se(r == ret); +} + +static void test_dns_name_between(void) { + /* see https://tools.ietf.org/html/rfc4034#section-6.1 + Note that we use "\033.z.example" in stead of "\001.z.example" as we + consider the latter invalid */ + test_dns_name_between_one("example", "a.example", "yljkjljk.a.example", true); + test_dns_name_between_one("a.example", "yljkjljk.a.example", "Z.a.example", true); + test_dns_name_between_one("yljkjljk.a.example", "Z.a.example", "zABC.a.EXAMPLE", true); + test_dns_name_between_one("Z.a.example", "zABC.a.EXAMPLE", "z.example", true); + test_dns_name_between_one("zABC.a.EXAMPLE", "z.example", "\\033.z.example", true); + test_dns_name_between_one("z.example", "\\033.z.example", "*.z.example", true); + test_dns_name_between_one("\\033.z.example", "*.z.example", "\\200.z.example", true); + test_dns_name_between_one("*.z.example", "\\200.z.example", "example", true); + test_dns_name_between_one("\\200.z.example", "example", "a.example", true); + + test_dns_name_between_one("example", "a.example", "example", -EINVAL); + test_dns_name_between_one("example", "example", "yljkjljk.a.example", false); + test_dns_name_between_one("example", "yljkjljk.a.example", "yljkjljk.a.example", false); +} + static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { assert_se(dns_name_endswith(a, b) == ret); } @@ -175,15 +247,19 @@ static void test_dns_name_reverse_one(const char *address, const char *name) { static void test_dns_name_reverse(void) { test_dns_name_reverse_one("47.11.8.15", "15.8.11.47.in-addr.arpa"); test_dns_name_reverse_one("fe80::47", "7.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa"); + test_dns_name_reverse_one("127.0.0.1", "1.0.0.127.in-addr.arpa"); + test_dns_name_reverse_one("::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"); } int main(int argc, char *argv[]) { test_dns_label_unescape(); + test_dns_label_unescape_suffix(); test_dns_label_escape(); test_dns_name_normalize(); test_dns_name_equal(); test_dns_name_endswith(); + test_dns_name_between(); test_dns_name_root(); test_dns_name_single_label(); test_dns_name_reverse(); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 4c31b776bd..be3a87958f 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -302,17 +302,27 @@ static void test_write_string_stream(void) { f = fdopen(fd, "r"); assert_se(f); - assert_se(write_string_stream(f, "boohoo") < 0); + assert_se(write_string_stream(f, "boohoo", true) < 0); f = freopen(fn, "r+", f); assert_se(f); - assert_se(write_string_stream(f, "boohoo") == 0); + assert_se(write_string_stream(f, "boohoo", true) == 0); rewind(f); assert_se(fgets(buf, sizeof(buf), f)); assert_se(streq(buf, "boohoo\n")); + f = freopen(fn, "w+", f); + assert_se(f); + + assert_se(write_string_stream(f, "boohoo", false) == 0); + rewind(f); + + assert_se(fgets(buf, sizeof(buf), f)); + printf(">%s<", buf); + assert_se(streq(buf, "boohoo")); + unlink(fn); } @@ -324,7 +334,7 @@ static void test_write_string_file(void) { fd = mkostemp_safe(fn, O_RDWR); assert_se(fd >= 0); - assert_se(write_string_file(fn, "boohoo") == 0); + assert_se(write_string_file(fn, "boohoo", WRITE_STRING_FILE_CREATE) == 0); assert_se(read(fd, buf, sizeof(buf)) == 7); assert_se(streq(buf, "boohoo\n")); @@ -340,8 +350,8 @@ static void test_write_string_file_no_create(void) { fd = mkostemp_safe(fn, O_RDWR); assert_se(fd >= 0); - assert_se(write_string_file_no_create("/a/file/which/does/not/exists/i/guess", "boohoo") < 0); - assert_se(write_string_file_no_create(fn, "boohoo") == 0); + assert_se(write_string_file("/a/file/which/does/not/exists/i/guess", "boohoo", 0) < 0); + assert_se(write_string_file(fn, "boohoo", 0) == 0); assert_se(read(fd, buf, sizeof(buf)) == strlen("boohoo\n")); assert_se(streq(buf, "boohoo\n")); @@ -367,8 +377,8 @@ static void test_load_env_file_pairs(void) { "ANSI_COLOR=\"0;36\"\n" "HOME_URL=\"https://www.archlinux.org/\"\n" "SUPPORT_URL=\"https://bbs.archlinux.org/\"\n" - "BUG_REPORT_URL=\"https://bugs.archlinux.org/\"\n" - ); + "BUG_REPORT_URL=\"https://bugs.archlinux.org/\"\n", + WRITE_STRING_FILE_CREATE); assert_se(r == 0); f = fdopen(fd, "r"); diff --git a/src/test/test-util.c b/src/test/test-util.c index ad9ea3bcce..f43433baa1 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -390,6 +390,39 @@ static void test_unhexchar(void) { assert_se(unhexchar('0') == 0x0); } +static void test_base32hexchar(void) { + assert_se(base32hexchar(0) == '0'); + assert_se(base32hexchar(9) == '9'); + assert_se(base32hexchar(10) == 'A'); + assert_se(base32hexchar(31) == 'V'); +} + +static void test_unbase32hexchar(void) { + assert_se(unbase32hexchar('0') == 0); + assert_se(unbase32hexchar('9') == 9); + assert_se(unbase32hexchar('A') == 10); + assert_se(unbase32hexchar('V') == 31); + assert_se(unbase32hexchar('=') == -EINVAL); +} + +static void test_base64char(void) { + assert_se(base64char(0) == 'A'); + assert_se(base64char(26) == 'a'); + assert_se(base64char(63) == '/'); +} + +static void test_unbase64char(void) { + assert_se(unbase64char('A') == 0); + assert_se(unbase64char('Z') == 25); + assert_se(unbase64char('a') == 26); + assert_se(unbase64char('z') == 51); + assert_se(unbase64char('0') == 52); + assert_se(unbase64char('9') == 61); + assert_se(unbase64char('+') == 62); + assert_se(unbase64char('/') == 63); + assert_se(unbase64char('=') == -EINVAL); +} + static void test_octchar(void) { assert_se(octchar(00) == '0'); assert_se(octchar(07) == '7'); @@ -410,6 +443,264 @@ static void test_undecchar(void) { assert_se(undecchar('9') == 9); } +static void test_unhexmem(void) { + const char *hex = "efa214921"; + const char *hex_invalid = "efa214921o"; + _cleanup_free_ char *hex2 = NULL; + _cleanup_free_ void *mem = NULL; + size_t len; + + assert_se(unhexmem(hex, strlen(hex), &mem, &len) == 0); + assert_se(unhexmem(hex, strlen(hex) + 1, &mem, &len) == -EINVAL); + assert_se(unhexmem(hex_invalid, strlen(hex_invalid), &mem, &len) == -EINVAL); + + assert_se((hex2 = hexmem(mem, len))); + + free(mem); + + assert_se(memcmp(hex, hex2, strlen(hex)) == 0); + + free(hex2); + + assert_se(unhexmem(hex, strlen(hex) - 1, &mem, &len) == 0); + assert_se((hex2 = hexmem(mem, len))); + assert_se(memcmp(hex, hex2, strlen(hex) - 1) == 0); +} + +/* https://tools.ietf.org/html/rfc4648#section-10 */ +static void test_base32hexmem(void) { + char *b32; + + b32 = base32hexmem("", strlen(""), true); + assert_se(b32); + assert_se(streq(b32, "")); + free(b32); + + b32 = base32hexmem("f", strlen("f"), true); + assert_se(b32); + assert_se(streq(b32, "CO======")); + free(b32); + + b32 = base32hexmem("fo", strlen("fo"), true); + assert_se(b32); + assert_se(streq(b32, "CPNG====")); + free(b32); + + b32 = base32hexmem("foo", strlen("foo"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMU===")); + free(b32); + + b32 = base32hexmem("foob", strlen("foob"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMUOG=")); + free(b32); + + b32 = base32hexmem("fooba", strlen("fooba"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1")); + free(b32); + + b32 = base32hexmem("foobar", strlen("foobar"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1E8======")); + free(b32); + + b32 = base32hexmem("", strlen(""), false); + assert_se(b32); + assert_se(streq(b32, "")); + free(b32); + + b32 = base32hexmem("f", strlen("f"), false); + assert_se(b32); + assert_se(streq(b32, "CO")); + free(b32); + + b32 = base32hexmem("fo", strlen("fo"), false); + assert_se(b32); + assert_se(streq(b32, "CPNG")); + free(b32); + + b32 = base32hexmem("foo", strlen("foo"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMU")); + free(b32); + + b32 = base32hexmem("foob", strlen("foob"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMUOG")); + free(b32); + + b32 = base32hexmem("fooba", strlen("fooba"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1")); + free(b32); + + b32 = base32hexmem("foobar", strlen("foobar"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1E8")); + free(b32); +} + +static void test_unbase32hexmem(void) { + void *mem; + size_t len; + + assert_se(unbase32hexmem("", strlen(""), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "")); + free(mem); + + assert_se(unbase32hexmem("CO======", strlen("CO======"), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "f")); + free(mem); + + assert_se(unbase32hexmem("CPNG====", strlen("CPNG===="), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fo")); + free(mem); + + assert_se(unbase32hexmem("CPNMU===", strlen("CPNMU==="), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foo")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOG=", strlen("CPNMUOG="), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foob")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOJ1", strlen("CPNMUOJ1"), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fooba")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOJ1E8======", strlen("CPNMUOJ1E8======"), true, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foobar")); + free(mem); + + assert_se(unbase32hexmem("A", strlen("A"), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("A=======", strlen("A======="), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAA=====", strlen("AAA====="), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAAAA==", strlen("AAAAAA=="), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AB======", strlen("AB======"), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAB====", strlen("AAAB===="), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAAB===", strlen("AAAAB==="), true, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAAAAB=", strlen("AAAAAAB="), true, &mem, &len) == -EINVAL); + + assert_se(unbase32hexmem("", strlen(""), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "")); + free(mem); + + assert_se(unbase32hexmem("CO", strlen("CO"), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "f")); + free(mem); + + assert_se(unbase32hexmem("CPNG", strlen("CPNG"), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fo")); + free(mem); + + assert_se(unbase32hexmem("CPNMU", strlen("CPNMU"), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foo")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOG", strlen("CPNMUOG"), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foob")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOJ1", strlen("CPNMUOJ1"), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fooba")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOJ1E8", strlen("CPNMUOJ1E8"), false, &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foobar")); + free(mem); + + assert_se(unbase32hexmem("CPNMUOG=", strlen("CPNMUOG="), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("CPNMUOJ1E8======", strlen("CPNMUOJ1E8======"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("A", strlen("A"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("A", strlen("A"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAA", strlen("AAA"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAAAA", strlen("AAAAAA"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AB", strlen("AB"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAB", strlen("AAAB"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAAB", strlen("AAAAB"), false, &mem, &len) == -EINVAL); + assert_se(unbase32hexmem("AAAAAAB", strlen("AAAAAAB"), false, &mem, &len) == -EINVAL); +} + +/* https://tools.ietf.org/html/rfc4648#section-10 */ +static void test_base64mem(void) { + char *b64; + + b64 = base64mem("", strlen("")); + assert_se(b64); + assert_se(streq(b64, "")); + free(b64); + + b64 = base64mem("f", strlen("f")); + assert_se(b64); + assert_se(streq(b64, "Zg==")); + free(b64); + + b64 = base64mem("fo", strlen("fo")); + assert_se(b64); + assert_se(streq(b64, "Zm8=")); + free(b64); + + b64 = base64mem("foo", strlen("foo")); + assert_se(b64); + assert_se(streq(b64, "Zm9v")); + free(b64); + + b64 = base64mem("foob", strlen("foob")); + assert_se(b64); + assert_se(streq(b64, "Zm9vYg==")); + free(b64); + + b64 = base64mem("fooba", strlen("fooba")); + assert_se(b64); + assert_se(streq(b64, "Zm9vYmE=")); + free(b64); + + b64 = base64mem("foobar", strlen("foobar")); + assert_se(b64); + assert_se(streq(b64, "Zm9vYmFy")); + free(b64); +} + +static void test_unbase64mem(void) { + void *mem; + size_t len; + + assert_se(unbase64mem("", strlen(""), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "")); + free(mem); + + assert_se(unbase64mem("Zg==", strlen("Zg=="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "f")); + free(mem); + + assert_se(unbase64mem("Zm8=", strlen("Zm8="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fo")); + free(mem); + + assert_se(unbase64mem("Zm9v", strlen("Zm9v"), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foo")); + free(mem); + + assert_se(unbase64mem("Zm9vYg==", strlen("Zm9vYg=="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foob")); + free(mem); + + assert_se(unbase64mem("Zm9vYmE=", strlen("Zm9vYmE="), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "fooba")); + free(mem); + + assert_se(unbase64mem("Zm9vYmFy", strlen("Zm9vYmFy"), &mem, &len) == 0); + assert_se(streq(strndupa(mem, len), "foobar")); + free(mem); + + assert_se(unbase64mem("A", strlen("A"), &mem, &len) == -EINVAL); + assert_se(unbase64mem("A====", strlen("A===="), &mem, &len) == -EINVAL); + assert_se(unbase64mem("AAB==", strlen("AAB=="), &mem, &len) == -EINVAL); + assert_se(unbase64mem("AAAB=", strlen("AAAB="), &mem, &len) == -EINVAL); +} + static void test_cescape(void) { _cleanup_free_ char *escaped; @@ -565,14 +856,14 @@ static void test_read_hostname_config(void) { close(fd); /* simple hostname */ - write_string_file(path, "foo"); + write_string_file(path, "foo", WRITE_STRING_FILE_CREATE); assert_se(read_hostname_config(path, &hostname) == 0); assert_se(streq(hostname, "foo")); free(hostname); hostname = NULL; /* with comment */ - write_string_file(path, "# comment\nfoo"); + write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE); assert_se(read_hostname_config(path, &hostname) == 0); assert_se(hostname); assert_se(streq(hostname, "foo")); @@ -580,7 +871,7 @@ static void test_read_hostname_config(void) { hostname = NULL; /* with comment and extra whitespace */ - write_string_file(path, "# comment\n\n foo "); + write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE); assert_se(read_hostname_config(path, &hostname) == 0); assert_se(hostname); assert_se(streq(hostname, "foo")); @@ -588,7 +879,7 @@ static void test_read_hostname_config(void) { hostname = NULL; /* cleans up name */ - write_string_file(path, "!foo/bar.com"); + write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE); assert_se(read_hostname_config(path, &hostname) == 0); assert_se(hostname); assert_se(streq(hostname, "foobar.com")); @@ -597,7 +888,7 @@ static void test_read_hostname_config(void) { /* no value set */ hostname = (char*) 0x1234; - write_string_file(path, "# nothing here\n"); + write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE); assert_se(read_hostname_config(path, &hostname) == -ENOENT); assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ @@ -1191,11 +1482,11 @@ static void test_execute_directory(void) { masked = strjoina(template_lo, "/masked"); mask = strjoina(template_hi, "/masked"); - assert_se(write_string_file(name, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works") == 0); - assert_se(write_string_file(name2, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2") == 0); - assert_se(write_string_file(overridden, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed") == 0); - assert_se(write_string_file(override, "#!/bin/sh\necho 'Executing '$0") == 0); - assert_se(write_string_file(masked, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed") == 0); + assert_se(write_string_file(name, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(overridden, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(override, "#!/bin/sh\necho 'Executing '$0", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(masked, "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", WRITE_STRING_FILE_CREATE) == 0); assert_se(symlink("/dev/null", mask) == 0); assert_se(chmod(name, 0755) == 0); assert_se(chmod(name2, 0755) == 0); @@ -1398,6 +1689,17 @@ static void test_unquote_first_word(void) { assert_se(streq(t, "\\w+\b")); free(t); assert_se(p == original + 5); + + p = original = "-N ''"; + assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0); + assert_se(streq(t, "-N")); + free(t); + assert_se(p == original + 3); + + assert_se(unquote_first_word(&p, &t, UNQUOTE_CUNESCAPE) > 0); + assert_se(streq(t, "")); + free(t); + assert_se(p == original + 5); } static void test_unquote_first_word_and_warn(void) { @@ -1804,10 +2106,19 @@ int main(int argc, char *argv[]) { test_in_charset(); test_hexchar(); test_unhexchar(); + test_base32hexchar(); + test_unbase32hexchar(); + test_base64char(); + test_unbase64char(); test_octchar(); test_unoctchar(); test_decchar(); test_undecchar(); + test_unhexmem(); + test_base32hexmem(); + test_unbase32hexmem(); + test_base64mem(); + test_unbase64mem(); test_cescape(); test_cunescape(); test_foreach_word(); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 42f757c4b7..271984b5a8 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -955,9 +955,10 @@ static int path_set_attribute(Item *item, const char *path) { r = chattr_fd(fd, f, item->attribute_mask); if (r < 0) - return log_error_errno(r, - "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m", - path, item->attribute_value, item->attribute_mask); + log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING, + r, + "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m", + path, item->attribute_value, item->attribute_mask); return 0; } diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index 7ba0b7fc8f..c6a2c56e77 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -409,7 +409,6 @@ int main(int argc, char *argv[]) union { uint8_t byte[512]; uint16_t wyde[256]; - uint64_t octa[64]; } identify; char model[41]; char model_enc[256]; @@ -638,10 +637,20 @@ int main(int argc, char *argv[]) * All other values are reserved. */ word = identify.wyde[108]; - if ((word & 0xf000) == 0x5000) + if ((word & 0xf000) == 0x5000) { + uint64_t wwwn; + + wwwn = identify.wyde[108]; + wwwn <<= 16; + wwwn |= identify.wyde[109]; + wwwn <<= 16; + wwwn |= identify.wyde[110]; + wwwn <<= 16; + wwwn |= identify.wyde[111]; printf("ID_WWN=0x%1$" PRIx64 "\n" "ID_WWN_WITH_EXTENSION=0x%1$" PRIx64 "\n", - identify.octa[108/4]); + wwwn); + } /* from Linux's include/linux/ata.h */ if (identify.wyde[0] == 0x848a || diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index b656204c46..72109d93d2 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -33,7 +33,7 @@ static sd_hwdb *hwdb; int udev_builtin_hwdb_lookup(struct udev_device *dev, const char *prefix, const char *modalias, const char *filter, bool test) { - _cleanup_free_ const char *lookup = NULL; + _cleanup_free_ char *lookup = NULL; const char *key, *value; int n = 0; diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index fabc653800..4f625251d6 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -52,7 +52,7 @@ void udev_builtin_init(struct udev *udev) { return; for (i = 0; i < ELEMENTSOF(builtins); i++) - if (builtins[i]->init) + if (builtins[i] && builtins[i]->init) builtins[i]->init(udev); initialized = true; @@ -65,7 +65,7 @@ void udev_builtin_exit(struct udev *udev) { return; for (i = 0; i < ELEMENTSOF(builtins); i++) - if (builtins[i]->exit) + if (builtins[i] && builtins[i]->exit) builtins[i]->exit(udev); initialized = false; @@ -75,7 +75,7 @@ bool udev_builtin_validate(struct udev *udev) { unsigned int i; for (i = 0; i < ELEMENTSOF(builtins); i++) - if (builtins[i]->validate && builtins[i]->validate(udev)) + if (builtins[i] && builtins[i]->validate && builtins[i]->validate(udev)) return true; return false; } @@ -84,14 +84,21 @@ void udev_builtin_list(struct udev *udev) { unsigned int i; for (i = 0; i < ELEMENTSOF(builtins); i++) - fprintf(stderr, " %-14s %s\n", builtins[i]->name, builtins[i]->help); + if (builtins[i]) + fprintf(stderr, " %-14s %s\n", builtins[i]->name, builtins[i]->help); } const char *udev_builtin_name(enum udev_builtin_cmd cmd) { + if (!builtins[cmd]) + return NULL; + return builtins[cmd]->name; } bool udev_builtin_run_once(enum udev_builtin_cmd cmd) { + if (!builtins[cmd]) + return false; + return builtins[cmd]->run_once; } @@ -105,7 +112,7 @@ enum udev_builtin_cmd udev_builtin_lookup(const char *command) { if (pos) pos[0] = '\0'; for (i = 0; i < ELEMENTSOF(builtins); i++) - if (streq(builtins[i]->name, name)) + if (builtins[i] && streq(builtins[i]->name, name)) return i; return UDEV_BUILTIN_MAX; } @@ -115,6 +122,9 @@ int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const c int argc; char *argv[128]; + if (!builtins[cmd]) + return -EOPNOTSUPP; + /* we need '0' here to reset the internal state */ optind = 0; strscpy(arg, sizeof(arg), command); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index e27fb1fd9e..d0b8bad48e 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -398,7 +398,7 @@ static void worker_spawn(Manager *manager, struct event *event) { prctl(PR_SET_PDEATHSIG, SIGTERM); /* reset OOM score, we only protect the main daemon */ - write_string_file("/proc/self/oom_score_adj", "0"); + write_string_file("/proc/self/oom_score_adj", "0", 0); for (;;) { struct udev_event *udev_event; @@ -1091,7 +1091,7 @@ static int synthesize_change(struct udev_device *dev) { */ log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); - write_string_file(filename, "change"); + write_string_file(filename, "change", WRITE_STRING_FILE_CREATE); udev_list_entry_foreach(item, udev_enumerate_get_list_entry(e)) { _cleanup_udev_device_unref_ struct udev_device *d = NULL; @@ -1106,7 +1106,7 @@ static int synthesize_change(struct udev_device *dev) { log_debug("device %s closed, synthesising partition '%s' 'change'", udev_device_get_devnode(dev), udev_device_get_devnode(d)); strscpyl(filename, sizeof(filename), udev_device_get_syspath(d), "/uevent", NULL); - write_string_file(filename, "change"); + write_string_file(filename, "change", WRITE_STRING_FILE_CREATE); } return 0; @@ -1114,7 +1114,7 @@ static int synthesize_change(struct udev_device *dev) { log_debug("device %s closed, synthesising 'change'", udev_device_get_devnode(dev)); strscpyl(filename, sizeof(filename), udev_device_get_syspath(dev), "/uevent", NULL); - write_string_file(filename, "change"); + write_string_file(filename, "change", WRITE_STRING_FILE_CREATE); return 0; } @@ -1358,6 +1358,7 @@ static int listen_fds(int *rctrl, int *rnetlink) { * udev.event-timeout=<number of seconds> seconds to wait before terminating an event */ static int parse_proc_cmdline_item(const char *key, const char *value) { + const char *full_key = key; int r; assert(key); @@ -1377,26 +1378,29 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { int prio; prio = util_log_priority(value); + if (prio < 0) + goto invalid; log_set_max_level(prio); } else if (streq(key, "children-max")) { r = safe_atou(value, &arg_children_max); if (r < 0) - log_warning("invalid udev.children-max ignored: %s", value); + goto invalid; } else if (streq(key, "exec-delay")) { r = safe_atoi(value, &arg_exec_delay); if (r < 0) - log_warning("invalid udev.exec-delay ignored: %s", value); + goto invalid; } else if (streq(key, "event-timeout")) { r = safe_atou64(value, &arg_event_timeout_usec); if (r < 0) - log_warning("invalid udev.event-timeout ignored: %s", value); - else { - arg_event_timeout_usec *= USEC_PER_SEC; - arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; - } + goto invalid; + arg_event_timeout_usec *= USEC_PER_SEC; + arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; } return 0; +invalid: + log_warning("invalid %s ignored: %s", full_key, value); + return 0; } static void help(void) { @@ -1432,7 +1436,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "c:de:DtN:hV", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) { int r; switch (c) { @@ -1747,7 +1751,7 @@ int main(int argc, char *argv[]) { setsid(); - write_string_file("/proc/self/oom_score_adj", "-1000"); + write_string_file("/proc/self/oom_score_adj", "-1000", 0); } r = run(fd_ctrl, fd_uevent, cgroup); diff --git a/src/user-sessions/user-sessions.c b/src/user-sessions/user-sessions.c index 1c31769fde..e80a7771de 100644 --- a/src/user-sessions/user-sessions.c +++ b/src/user-sessions/user-sessions.c @@ -65,7 +65,7 @@ int main(int argc, char*argv[]) { } else if (streq(argv[1], "stop")) { int r; - r = write_string_file_atomic("/run/nologin", "System is going down."); + r = write_string_file("/run/nologin", "System is going down.", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); if (r < 0) { log_error_errno(r, "Failed to create /run/nologin: %m"); return EXIT_FAILURE; diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index f7728dcfff..7bdc158ad7 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -56,7 +56,7 @@ static int disable_utf8(int fd) { if (k < 0) r = k; - k = write_string_file("/sys/module/vt/parameters/default_utf8", "0"); + k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); if (k < 0) r = k; @@ -89,7 +89,7 @@ static int enable_utf8(int fd) { if (k < 0) r = k; - k = write_string_file("/sys/module/vt/parameters/default_utf8", "1"); + k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); if (k < 0) r = k; |