Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rebootmgr for openSUSE:Factory checked in at 2026-10-30 18:03:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rebootmgr (Old) and /work/SRC/openSUSE:Factory/.rebootmgr.new.1255 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rebootmgr" Fri Oct 30 18:03:37 2026 rev:38 rq:1393609 version:4.3 Changes: -------- --- /work/SRC/openSUSE:Factory/rebootmgr/rebootmgr.changes 2026-10-09 21:42:53.312031280 +0200 +++ /work/SRC/openSUSE:Factory/.rebootmgr.new.1255/rebootmgr.changes 2026-10-30 18:04:06.934705840 +0100 @@ -1,0 +2,27 @@ +Thu Oct 29 11:24:18 UTC 2026 - Thorsten Kukuk <[email protected]> + +- Update to version 4.3: + * Release version 4.3 + * Use logind varlink interface for reboot + * rebootmgrd: more error handler fixes + +------------------------------------------------------------------- +Tue Oct 27 11:03:10 UTC 2026 - Thorsten Kukuk <[email protected]> + +- Update to version 4.2: + * Release version 4.2 + * Fix error handling in case SELinux denies reboot [bsc#1287746] + +------------------------------------------------------------------- +Fri Oct 23 13:08:26 UTC 2026 - Thorsten Kukuk <[email protected]> + +- Update to version 4.1: + * Release version 4.1 + * Use varlink for reboot, else posix_spawn + * Update basics.h + * calendarspec: check for error when resetting TZ + * Fix URL of github project + * supporting time_zones + * initialize seed with random value + +------------------------------------------------------------------- Old: ---- rebootmgr-4.0+git20261008.76d8ee7.tar.xz New: ---- rebootmgr-4.3.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rebootmgr.spec ++++++ --- /var/tmp/diff_new_pack.jeaDCW/_old 2026-10-30 18:04:09.868828703 +0100 +++ /var/tmp/diff_new_pack.jeaDCW/_new 2026-10-30 18:04:09.878829122 +0100 @@ -17,7 +17,7 @@ Name: rebootmgr -Version: 4.0+git20261008.76d8ee7 +Version: 4.3 Release: 0 Summary: Automatic controlled reboot during a maintenance window License: GPL-2.0-only AND LGPL-2.1-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.jeaDCW/_old 2026-10-30 18:04:10.015834859 +0100 +++ /var/tmp/diff_new_pack.jeaDCW/_new 2026-10-30 18:04:10.028835403 +0100 @@ -1,8 +1,8 @@ <services> <service name="tar_scm" mode="manual"> <param name="url">https://github.com/SUSE/rebootmgr.git</param> - <param name="versionformat">@PARENT_TAG@+git%cd.%h</param> - <param name="versionrewrite-pattern">v(.*?)(\+git0)?$</param> + <param name="versionformat">@PARENT_TAG@+git%cd.@TAG_OFFSET@</param> + <param name="versionrewrite-pattern">v(.*?)(\+git[0-9]{8}\.0)?$</param> <param name="revision">master</param> <param name="scm">git</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.jeaDCW/_old 2026-10-30 18:04:10.144840261 +0100 +++ /var/tmp/diff_new_pack.jeaDCW/_new 2026-10-30 18:04:10.155840721 +0100 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/SUSE/rebootmgr.git</param> - <param name="changesrevision">76d8ee7e7301b90403eed4b91ef15f08762d3dac</param></service> + <param name="changesrevision">1273fab3f26c32cbabde1c8fe0c6d6131bb286a8</param></service> </servicedata> (No newline at EOF) ++++++ rebootmgr-4.0+git20261008.76d8ee7.tar.xz -> rebootmgr-4.3.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/NEWS new/rebootmgr-4.3/NEWS --- old/rebootmgr-4.0+git20261008.76d8ee7/NEWS 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/NEWS 2026-10-29 12:23:33.000000000 +0100 @@ -2,6 +2,17 @@ Copyright (C) 2016-2026 Thorsten Kukuk +Version 4.3 +* Use logind varlink interface for reboot + +Version 4.2 +* Fix error handling if SELinux denies reboot + +Version 4.1 +* Use varlink for reboot, else fallback to systemctl via posix_spwan +* Support timezones for maintenance window +* Bugfix: initialize seed with random value + Version 4.0 * Create symlink for varlink registry * Add metrics for systemd-report diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/calendarspec.c new/rebootmgr-4.3/lib/calendarspec/calendarspec.c --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/calendarspec.c 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/calendarspec/calendarspec.c 2026-10-29 12:23:33.000000000 +0100 @@ -24,10 +24,8 @@ #include <stdlib.h> #include <string.h> -// #include "alloc-util.h" +#include "basics.h" #include "calendarspec.h" -// #include "fileio.h" -// #include "string-util.h" #define assert_se assert @@ -45,13 +43,6 @@ } /* from basic/string-util.h */ -#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) - -static inline bool isempty(const char *p) { - return !p || !p[0]; -} - -/* from basic/string-util.h */ static inline const char *startswith_no_case(const char *s, const char *prefix) { size_t l; @@ -106,6 +97,8 @@ if (!c) return; + free(c->timezone); + free_chain(c->year); free_chain(c->month); free_chain(c->day); @@ -346,6 +339,10 @@ if (c->utc) fputs(" UTC", f); + else if (c->timezone) { + fputc(' ', f); + fputs(c->timezone, f); + } r = fflush_and_check(f); if (r < 0) { @@ -730,6 +727,19 @@ if (utc) { c->utc = true; p = strndupa(p, utc - p); + } else { + const char *last_space; + + last_space = strrchr(p, ' '); + if (last_space && timezone_is_valid(last_space + 1)) { + c->timezone = strdup(last_space + 1); + if (!c->timezone) { + r = -ENOMEM; + goto fail; + } + + p = strndupa(p, last_space - p); + } } if (strcaseeq(p, "minutely")) { @@ -1061,21 +1071,44 @@ struct tm tm; time_t t; int r; + const char *old_tz = NULL; assert(spec); assert(next); + if (spec->timezone) { + old_tz = getenv("TZ"); + if (old_tz) + old_tz = strdupa(old_tz); + + if (setenv("TZ", spec->timezone, 1) < 0) + return -errno; + tzset(); + } + t = (time_t) (usec / USEC_PER_SEC) + 1; assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); r = find_next(spec, &tm); if (r < 0) - return r; + goto finish; t = mktime_or_timegm(&tm, spec->utc); - if (t == (time_t) -1) - return -EINVAL; + if (t == (time_t) -1) { + r = -EINVAL; + goto finish; + } *next = (usec_t) t * USEC_PER_SEC; - return 0; + r = 0; + +finish: + if (spec->timezone) { + int rr = old_tz ? setenv("TZ", old_tz, 1) : unsetenv("TZ"); + if (rr < 0 && r == 0) + r = -errno; + tzset(); + } + + return r; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/calendarspec.h new/rebootmgr-4.3/lib/calendarspec/calendarspec.h --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/calendarspec.h 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/calendarspec/calendarspec.h 2026-10-29 12:23:33.000000000 +0100 @@ -38,6 +38,7 @@ typedef struct CalendarSpec { int weekdays_bits; bool utc; + char *timezone; CalendarComponent *year; CalendarComponent *month; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/meson.build new/rebootmgr-4.3/lib/calendarspec/meson.build --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/meson.build 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/calendarspec/meson.build 2026-10-29 12:23:33.000000000 +0100 @@ -3,4 +3,5 @@ libcalendarspec_a = static_library( 'libcalendarspec', libcalendarspec_c, + include_directories : inc, install : false) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/time-util.c new/rebootmgr-4.3/lib/calendarspec/time-util.c --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/time-util.c 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/calendarspec/time-util.c 2026-10-29 12:23:33.000000000 +0100 @@ -20,6 +20,9 @@ ***/ #include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> #define assert_se assert #include "time-util.h" @@ -48,6 +51,47 @@ } +bool timezone_is_valid(const char *name) { + bool slash = false; + const char *p; + char *t; + struct stat st; + bool ret; + + if (!name || !name[0]) + return false; + + if (name[0] == '/') + return false; + + for (p = name; *p; p++) { + if (!(*p >= '0' && *p <= '9') && + !(*p >= 'a' && *p <= 'z') && + !(*p >= 'A' && *p <= 'Z') && + !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) + return false; + + if (*p == '/') { + if (slash) + return false; + + slash = true; + } else + slash = false; + } + + if (slash) + return false; + + if (asprintf(&t, "/usr/share/zoneinfo/%s", name) < 0) + return false; + + ret = stat(t, &st) >= 0 && S_ISREG(st.st_mode); + free(t); + + return ret; +} + time_t mktime_or_timegm(struct tm *tm, bool utc) { return utc ? timegm(tm) : mktime(tm); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/time-util.h new/rebootmgr-4.3/lib/calendarspec/time-util.h --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/calendarspec/time-util.h 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/calendarspec/time-util.h 2026-10-29 12:23:33.000000000 +0100 @@ -27,14 +27,14 @@ #include <time.h> #include <sys/time.h> +#include "basics.h" + typedef uint64_t usec_t; typedef uint64_t nsec_t; #define NSEC_FMT "%" PRIu64 #define USEC_FMT "%" PRIu64 -//#include "macro.h" -#define _pure_ __attribute__ ((pure)) #define ELEMENTSOF(x) \ __extension__ (__builtin_choose_expr( \ !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/basics.h new/rebootmgr-4.3/lib/common/basics.h --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/basics.h 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/common/basics.h 2026-10-29 12:23:33.000000000 +0100 @@ -1,25 +1,32 @@ -//SPDX-License-Identifier: GPL-2.0-or-later - -/* Copyright (c) 2024 Thorsten Kukuk - Author: Thorsten Kukuk <[email protected]> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, see <http://www.gnu.org/licenses/>. */ +//SPDX-License-Identifier: LGPL-2.1-or-later #pragma once -#define _cleanup_(x) __attribute__((__cleanup__(x))) -#define _unused_(x) x __attribute__((unused)) +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <stdbool.h> + +#define _unused_ __attribute__((unused)) +#define _pure_ __attribute__((__pure__)) +#define _const_ __attribute__((__const__)) +#define _sentinel_ __attribute__((__sentinel__)) + +/* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time + * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ +#define TAKE_GENERIC(var, type, nullvalue) \ + ({ \ + type *_pvar_ = &(var); \ + type _var_ = *_pvar_; \ + type _nullvalue_ = nullvalue; \ + *_pvar_ = _nullvalue_; \ + _var_; \ + }) +#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL) +#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr)) +#define TAKE_FD(fd) TAKE_GENERIC(fd, int, -EBADF) #define mfree(memory) \ ({ \ @@ -31,3 +38,48 @@ *(void**)p = mfree(*(void**) p); } +static inline void closep(int *fd) { + if (*fd >= 0) + close(*fd); + *fd = -EBADF; +} + +static inline void fclosep(FILE **f) { + if (*f) + fclose(*f); + *f = NULL; +} + +#define _cleanup_(x) __attribute__((__cleanup__(x))) +#define _cleanup_close_ _cleanup_(closep) +#define _cleanup_fclose_ _cleanup_(fclosep) +#define _cleanup_free_ _cleanup_(freep) + + +/* from string-util-fundamental.h */ + +#define WHITESPACE " \t\n\r" + +#define streq(a,b) (strcmp((a),(b)) == 0) +#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) +#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) +#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) + +static inline const char *strempty(const char *s) { + return s ?:""; +} + +static inline const char *strna(const char *s) { + return s ?: "n/a"; +} + +static inline const char *stroom(const char *s) { + return s ?: "Out of memory"; +} + +extern char *startswith(const char *s, const char *prefix) _pure_; +extern char *endswith(const char *s, const char *suffix) _pure_; + +static inline bool isempty(const char *a) { + return !a || a[0] == '\0'; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/exec_cmd.c new/rebootmgr-4.3/lib/common/exec_cmd.c --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/exec_cmd.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rebootmgr-4.3/lib/common/exec_cmd.c 2026-10-29 12:23:33.000000000 +0100 @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "config.h" + +#include <errno.h> +#include <stdarg.h> +#include <spawn.h> +#include <sys/wait.h> + +#include "basics.h" +#include "exec_cmd.h" + +extern char **environ; + +/* Executes a command using posix_spawnp and waits for it to finish. + If "output" is not NULL, the command's stdout is captured and + returned as a malloc'ed, NUL-terminated string in *output, which + the caller has to free. On failure, *output is set to NULL. + Returns 0 on success, -errno on failure, > 0 exit code of the + called command. */ +int +exec_cmd(char **output, char *const *extra_env, const char *cmd, ...) +{ + _cleanup_free_ char **argv = NULL; + _cleanup_free_ char **envp_buf = NULL; + char **envp = environ; + va_list args, args_copy; + int argc; + int r; + int pipefd[2] = { -1, -1 }; + posix_spawn_file_actions_t file_actions; + posix_spawn_file_actions_t *file_actionsp = NULL; + + if (output) + *output = NULL; + + argc = 0; + va_start(args, cmd); + va_copy(args_copy, args); + while (va_arg(args_copy, const char *) != NULL) + argc++; + va_end(args_copy); + + argv = malloc((argc + 1) * sizeof(char *)); + if (!argv) + { + va_end(args); + return -ENOMEM; + } + + for (int i = 0; i < argc; i++) + argv[i] = (char *)va_arg(args, const char *); + argv[argc] = NULL; + va_end(args); + + if (extra_env && extra_env[0]) + { + size_t env_count = 0; + while (environ[env_count]) + env_count++; + size_t extra_count = 0; + while (extra_env[extra_count]) + extra_count++; + + envp_buf = malloc((env_count + extra_count + 1) * sizeof(char *)); + if (!envp_buf) + return -ENOMEM; + + memcpy(envp_buf, environ, env_count * sizeof(char *)); + memcpy(envp_buf + env_count, extra_env, extra_count * sizeof(char *)); + envp_buf[env_count + extra_count] = NULL; + envp = envp_buf; + } + + if (output) + { + if (pipe(pipefd) == -1) + return -errno; + + r = posix_spawn_file_actions_init(&file_actions); + if (r != 0) + { + close(pipefd[0]); + close(pipefd[1]); + return -r; + } + file_actionsp = &file_actions; + + posix_spawn_file_actions_adddup2(&file_actions, pipefd[1], STDOUT_FILENO); + posix_spawn_file_actions_addclose(&file_actions, pipefd[0]); + posix_spawn_file_actions_addclose(&file_actions, pipefd[1]); + } + + pid_t pid; + + r = posix_spawnp(&pid, cmd, file_actionsp, NULL, argv, envp); + + if (file_actionsp) + posix_spawn_file_actions_destroy(file_actionsp); + + if (r != 0) + { + if (output) + { + close(pipefd[0]); + close(pipefd[1]); + } + return -r; + } + + _cleanup_free_ char *buf = NULL; + + if (output) + { + close(pipefd[1]); + + size_t size = 0; + size_t capacity = 0; + ssize_t n; + char chunk[4096]; + + while ((n = read(pipefd[0], chunk, sizeof(chunk))) > 0) + { + if (size + (size_t)n + 1 > capacity) + { + capacity = size + (size_t)n + 1; + char *tmp = realloc(buf, capacity); + if (!tmp) + { + close(pipefd[0]); + waitpid(pid, NULL, 0); + return -ENOMEM; + } + buf = tmp; + } + memcpy(buf + size, chunk, (size_t)n); + size += (size_t)n; + buf[size] = '\0'; + } + + close(pipefd[0]); + + if (!buf) + { + buf = strdup(""); + if (!buf) + { + waitpid(pid, NULL, 0); + return -ENOMEM; + } + } + } + + // Wait for the child process to complete + int wait_status; + while (waitpid(pid, &wait_status, 0) == -1) + { + if (errno == EINTR) + continue; + return -errno; + } + + // Evaluate the exit status + if (WIFEXITED(wait_status)) + { + int exit_code = WEXITSTATUS(wait_status); + if (exit_code == 0 && output) + *output = TAKE_PTR(buf); + if (exit_code != 0) + return exit_code; + return 0; + } + else if (WIFSIGNALED(wait_status)) + { + int sig = WTERMSIG(wait_status); + // Conventionally, shells return 128 + signal number for fatal signals + return 128 + sig; + } + + // fprintf(stderr, "Command '%s' terminated in an unknown state.\n", cmd); + return -EIO; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/exec_cmd.h new/rebootmgr-4.3/lib/common/exec_cmd.h --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/exec_cmd.h 1970-01-01 01:00:00.000000000 +0100 +++ new/rebootmgr-4.3/lib/common/exec_cmd.h 2026-10-29 12:23:33.000000000 +0100 @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "basics.h" + +/* The variadic argument list must be NULL-terminated. + "extra_env" is an optional NULL-terminated array of "NAME=VALUE" + strings which are added to the environment of the executed command; + it may be NULL if no extra environment variables are needed. */ +extern int exec_cmd(char **output, char *const *extra_env, const char *cmd, ...) _sentinel_; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/meson.build new/rebootmgr-4.3/lib/common/meson.build --- old/rebootmgr-4.0+git20261008.76d8ee7/lib/common/meson.build 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/lib/common/meson.build 2026-10-29 12:23:33.000000000 +0100 @@ -1,5 +1,5 @@ libcommon_c = ['load_config.c', 'save_config.c', 'mkdir_p.c', 'log_msg.c', - 'util.c'] + 'util.c', 'exec_cmd.c'] libcommon_a = static_library( 'libcommon', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/man/rebootmgr.conf.5.xml new/rebootmgr-4.3/man/rebootmgr.conf.5.xml --- old/rebootmgr-4.0+git20261008.76d8ee7/man/rebootmgr.conf.5.xml 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/man/rebootmgr.conf.5.xml 2026-10-29 12:23:33.000000000 +0100 @@ -52,7 +52,7 @@ <para> The format of <varname>window-start</varname> is the same as described in <citerefentry - project='systemd'><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. Except that timezones are not supported. + project='systemd'><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. </para> </listitem> </varlistentry> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/meson.build new/rebootmgr-4.3/meson.build --- old/rebootmgr-4.0+git20261008.76d8ee7/meson.build 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/meson.build 2026-10-29 12:23:33.000000000 +0100 @@ -13,7 +13,7 @@ 'c_std=gnu17', 'warning_level=2',], license : ['GPL-2.0-only', 'LGPL-2.1-or-later'], - version : '4.0', + version : '4.3', ) fs = import('fs') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/src/rebootmgrctl.c new/rebootmgr-4.3/src/rebootmgrctl.c --- old/rebootmgr-4.0+git20261008.76d8ee7/src/rebootmgrctl.c 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/src/rebootmgrctl.c 2026-10-29 12:23:33.000000000 +0100 @@ -19,19 +19,6 @@ #define _(String) gettext(String) #endif -/* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time - * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ -#define TAKE_GENERIC(var, type, nullvalue) \ - ({ \ - type *_pvar_ = &(var); \ - type _var_ = *_pvar_; \ - type _nullvalue_ = nullvalue; \ - *_pvar_ = _nullvalue_; \ - _var_; \ - }) -#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL) -#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr)) - static int connect_to_rebootmgr(sd_varlink **ret) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/src/rebootmgrd.c new/rebootmgr-4.3/src/rebootmgrd.c --- old/rebootmgr-4.0+git20261008.76d8ee7/src/rebootmgrd.c 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/src/rebootmgrd.c 2026-10-29 12:23:33.000000000 +0100 @@ -6,17 +6,23 @@ #include <stdlib.h> #include <stdbool.h> #include <libintl.h> +#include <time.h> +#include <unistd.h> +#include <sys/random.h> #include <systemd/sd-daemon.h> #include <systemd/sd-varlink.h> #include "basics.h" #include "common.h" #include "parse-duration.h" +#include "exec_cmd.h" #include "varlink-org.openSUSE.rebootmgr.h" #include "varlink-io.systemd.Metrics.h" #include "rebootmgrd-varlink-metrics.h" +#define VARLINK_REBOOT_SOCKET "/run/systemd/io.systemd.Login" + static int verbose_flag = 0; static int @@ -298,10 +304,93 @@ ctx->timer = sd_event_source_unref(ctx->timer); } +struct system_error +{ + char *origin; + char *errno_name; + int errno_val; +}; + +static void +system_error_free(struct system_error *e) +{ + e->origin = mfree(e->origin); + e->errno_name = mfree(e->errno_name); +} + +static int +varlink_reboot(const char *method) +{ + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + const char *error_id = NULL; + int r; + + r = sd_varlink_connect_address(&link, VARLINK_REBOOT_SOCKET); + if (r < 0) + { + log_msg(LOG_DEBUG, "Connecting to %s failed: %s", VARLINK_REBOOT_SOCKET, strerror(-r)); + return r; + } + + r = sd_varlink_call(link, method, NULL, &reply, &error_id); + if (r < 0) + { + log_msg(LOG_ERR, "varlink_reboot(%s) failed: %s", method, strerror(-r)); + return r; + } + if (!isempty(error_id)) + { + if (streq(error_id, "io.systemd.Shutdown.AlreadyInProgress")) + { + log_msg(LOG_NOTICE, "Reboot already in progress (systemd-logind)!"); + return 0; + } + + /* The common SD_VARLINK_ERROR_SYSTEM error wraps a plain Linux + errno; the useful detail is in its parameters, not in the + error ID itself. */ + if (streq(error_id, SD_VARLINK_ERROR_SYSTEM)) + { + _cleanup_(system_error_free) struct system_error e = { .errno_val = 0 }; + static const sd_json_dispatch_field dispatch_table[] = { + { "origin", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct system_error, origin), 0 }, + { "errnoName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct system_error, errno_name), 0 }, + { "errno", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct system_error, errno_val), 0 }, + {} + }; + + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &e); + if (r < 0) + { + log_msg(LOG_ERR, "sd_json_dispatch failed: %s", strerror(-r)); + if (debug_flag) + sd_json_variant_dump(reply, SD_JSON_FORMAT_NEWLINE, stdout, NULL); + log_msg(LOG_ERR, "varlink_reboot(%s) failed: %s", method, error_id); + } + else + { + if (!isempty(e.origin) && streq(e.origin, "linux")) + log_msg(LOG_ERR, "varlink_reboot(%s) failed: %s:%s", method, strna(e.errno_name), strerror(e.errno_val)); + else + log_msg(LOG_ERR, "varlink_reboot(%s) failed: %s:%s", method, strna(e.origin), strna(e.errno_name)); + return e.errno_val?-e.errno_val:-EACCES; + } + } + else + log_msg(LOG_ERR, "varlink_reboot(%s) failed: %s", method, error_id); + + return -EACCES; + } + + return 0; +} + static int time_handler(sd_event_source _unused_(*s), uint64_t _unused_(usec), void *userdata) { RM_CTX *ctx = userdata; + int r; if (debug_flag) log_msg(LOG_DEBUG, "Time handler for reboot called"); @@ -328,15 +417,16 @@ return -EINVAL; } + // Don't do a real reboot in debug mode if (debug_flag) { switch (ctx->reboot_method) { case RM_REBOOTMETHOD_HARD: - log_msg(LOG_DEBUG, "systemctl reboot called!"); + log_msg(LOG_DEBUG, "DEBUG: reboot requested!"); break; case RM_REBOOTMETHOD_SOFT: - log_msg(LOG_DEBUG, "systemctl soft-reboot called!"); + log_msg(LOG_DEBUG, "DEBUG: soft-reboot requested!"); break; default: /* cannot happen */ @@ -345,45 +435,55 @@ } else { - pid_t pid = fork(); - - if (pid < 0) - { - log_msg(LOG_ERR, "Calling /usr/bin/systemctl failed: %m"); - } - else if (pid == 0) - { - int r; + // Try varlink first without fork. + // Ignore return value, function will only return if it + // fails + switch (ctx->reboot_method) + { + case RM_REBOOTMETHOD_HARD: + r = varlink_reboot("io.systemd.Shutdown.Reboot"); + break; + case RM_REBOOTMETHOD_SOFT: + r = varlink_reboot("io.systemd.Shutdown.SoftReboot"); + break; + default: + /* cannot happen */ + r = -EINVAL; + break; + } + if (r < 0) + { + log_msg(LOG_ERR, "varlink_reboot failed, falling back to systemctl"); + // varlink failed, use systemctl switch (ctx->reboot_method) { case RM_REBOOTMETHOD_HARD: - char envar1[] = "SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT=1"; - char *env[] = {envar1, NULL}; - - r = execle("/usr/bin/systemctl", "systemctl", "reboot", - NULL, env); - + { + char *env[] = {"SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT=1", NULL}; + r = exec_cmd(NULL, env, "/usr/bin/systemctl", "systemctl", + "reboot", NULL); + } break; case RM_REBOOTMETHOD_SOFT: - r = execl("/usr/bin/systemctl", "systemctl", "soft-reboot", - NULL); + r = exec_cmd(NULL, NULL, "/usr/bin/systemctl", "systemctl", + "soft-reboot", NULL); break; default: /* cannot happen */ + r = -EINVAL; break; } if (r < 0) - { - log_msg(LOG_ERR, "Calling /usr/bin/systemctl %s failed: %m", - (ctx->reboot_method == RM_REBOOTMETHOD_HARD)?"reboot":"soft-reboot"); - exit(1); - } - exit(0); - } - } - - reset_timer(ctx); + log_msg(LOG_ERR, "Calling /usr/bin/systemctl %s failed: %s", + (ctx->reboot_method == RM_REBOOTMETHOD_HARD)?"reboot":"soft-reboot", + strerror(-r)); + else if (r > 0) + log_msg(LOG_ERR, "Calling /usr/bin/systemctl %s failed with exit code %i", + (ctx->reboot_method == RM_REBOOTMETHOD_HARD)?"reboot":"soft-reboot", r); + } + reset_timer(ctx); + } } return 0; @@ -873,7 +973,7 @@ r = sd_varlink_server_set_info(varlink_server, "openSUSE", PACKAGE" (rebootmgrd)", VERSION, - "https://github.com/thkukuk/rebootmgr3"); + "https://github.com/SUSE/rebootmgr"); if (r < 0) return r; @@ -1008,6 +1108,14 @@ log_init(); + /* Seed the PRNG used for the random reboot delay with real entropy. + Without this, every freshly started daemon uses the same libc + default seed and thus picks the exact same "random" delay. */ + unsigned int seed; + if (getrandom(&seed, sizeof(seed), 0) != sizeof(seed)) + seed = (unsigned int)time(NULL) ^ (unsigned int)getpid(); + srand(seed); + while (1) { int c; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rebootmgr-4.0+git20261008.76d8ee7/tests/test-calendarspec.c new/rebootmgr-4.3/tests/test-calendarspec.c --- old/rebootmgr-4.0+git20261008.76d8ee7/tests/test-calendarspec.c 2026-10-08 09:48:28.000000000 +0200 +++ new/rebootmgr-4.3/tests/test-calendarspec.c 2026-10-29 12:23:33.000000000 +0100 @@ -137,6 +137,8 @@ test_one("annually", "*-01-01 00:00:00"); test_one("*:2/3", "*-*-* *:02/3:00"); test_one("2015-10-25 01:00:00 uTc", "2015-10-25 01:00:00 UTC"); + test_one("2015-10-25 01:00:00 Europe/Berlin", "2015-10-25 01:00:00 Europe/Berlin"); + test_one("*-*-* 04:04:00 America/New_York", "*-*-* 04:04:00 America/New_York"); test_next("2016-03-27 03:17:00", "", 12345, 1459048620000000); test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000); @@ -146,10 +148,15 @@ test_next("2016-03-27 03:17:00 UTC", "CET", 12345, 1459048620000000); test_next("2016-03-27 03:17:00 UTC", "EET", 12345, 1459048620000000); + /* Explicit per-spec timezone, independent of the process-wide TZ */ + test_next("2016-03-27 03:17:00 Europe/Berlin", "EET", 12345, 1459041420000000); + test_next("2016-03-27 03:17:00 America/New_York", "UTC", 12345, 1459063020000000); + assert_se(calendar_spec_from_string("test", &c) < 0); assert_se(calendar_spec_from_string("", &c) < 0); assert_se(calendar_spec_from_string("7", &c) < 0); assert_se(calendar_spec_from_string("121212:1:2", &c) < 0); + assert_se(calendar_spec_from_string("04:04:00 Bogus/Zone", &c) < 0); return 0; }
