Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package zcfan for openSUSE:Factory checked in at 2025-01-01 23:07:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/zcfan (Old) and /work/SRC/openSUSE:Factory/.zcfan.new.1881 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "zcfan" Wed Jan 1 23:07:46 2025 rev:4 rq:1234116 version:1.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/zcfan/zcfan.changes 2024-02-29 21:50:25.094126622 +0100 +++ /work/SRC/openSUSE:Factory/.zcfan.new.1881/zcfan.changes 2025-01-01 23:07:53.656426634 +0100 @@ -1,0 +2,11 @@ +Tue Dec 31 08:30:07 UTC 2024 - Andrea Manzini <[email protected]> + +- update to 1.4.0: + * Mark zcfan.service as conflicting with thinkfan.service + * Make fan levels configurable in config + * On watchdog timeout, write to watchdog to avoid spinup on some models + * Detect suspend and handle fan level reset + +- mark as conflicts with thinkfan package + +------------------------------------------------------------------- Old: ---- zcfan-1.3.0.tar.gz New: ---- zcfan-1.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ zcfan.spec ++++++ --- /var/tmp/diff_new_pack.mblk5i/_old 2025-01-01 23:07:54.112445361 +0100 +++ /var/tmp/diff_new_pack.mblk5i/_new 2025-01-01 23:07:54.112445361 +0100 @@ -17,7 +17,7 @@ Name: zcfan -Version: 1.3.0 +Version: 1.4.0 Release: 0 Summary: Zero-configuration fan control daemon for ThinkPads License: MIT @@ -30,6 +30,7 @@ BuildRequires: gcc >= 11 %endif BuildRequires: make +Conflicts: thinkfan %description Zero-configuration fan control daemon for ThinkPads with watchdog ++++++ move_executable_to_sbin.patch ++++++ --- /var/tmp/diff_new_pack.mblk5i/_old 2025-01-01 23:07:54.136446347 +0100 +++ /var/tmp/diff_new_pack.mblk5i/_new 2025-01-01 23:07:54.140446511 +0100 @@ -1,25 +1,15 @@ -diff --color -ur zcfan-1.2.1.orig/Makefile zcfan-1.2.1/Makefile ---- zcfan-1.2.1.orig/Makefile 2022-08-12 03:50:01.000000000 +0200 -+++ zcfan-1.2.1/Makefile 2023-09-04 10:14:18.332038220 +0200 -@@ -5,7 +5,7 @@ +diff --color -ur zcfan-1.4.0.orig/Makefile zcfan-1.4.0/Makefile +--- zcfan-1.4.0.orig/Makefile 2024-12-18 01:36:27.000000000 +0100 ++++ zcfan-1.4.0/Makefile 2024-12-31 09:28:33.344935876 +0100 +@@ -5,8 +5,8 @@ + EXECUTABLES=$(patsubst %.c,%,$(SOURCES)) INSTALL:=install - prefix:=/usr/local +-prefix:=/usr/local -bindir:=$(prefix)/bin ++prefix:=/usr +bindir:=$(prefix)/sbin datarootdir:=$(prefix)/share mandir:=$(datarootdir)/man -diff --color -ur zcfan-1.2.1.orig/zcfan.service zcfan-1.2.1/zcfan.service ---- zcfan-1.2.1.orig/zcfan.service 2022-08-12 03:50:01.000000000 +0200 -+++ zcfan-1.2.1/zcfan.service 2023-09-04 10:15:28.486174496 +0200 -@@ -2,7 +2,7 @@ - Description=Zero-configuration fan control for ThinkPad - - [Service] --ExecStart=/usr/bin/zcfan -+ExecStart=/usr/sbin/zcfan - Restart=always - RestartSec=500ms - ++++++ zcfan-1.3.0.tar.gz -> zcfan-1.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zcfan-1.3.0/Makefile new/zcfan-1.4.0/Makefile --- old/zcfan-1.3.0/Makefile 2024-02-27 03:55:17.000000000 +0100 +++ new/zcfan-1.4.0/Makefile 2024-12-18 01:36:27.000000000 +0100 @@ -10,7 +10,13 @@ datarootdir:=$(prefix)/share mandir:=$(datarootdir)/man -all: $(EXECUTABLES) +SERVICE_TEMPLATE=zcfan.service.in +SERVICE=zcfan.service + +all: $(EXECUTABLES) $(SERVICE) + +$(SERVICE): $(SERVICE_TEMPLATE) + sed 's|@bindir@|$(bindir)|g' $< > $@ %: %.c $(CC) $(CPPFLAGS) $(CFLAGS) $< -o $@ $(LIBS) $(LDFLAGS) @@ -24,7 +30,7 @@ # Noisy clang build that's expected to fail, but can be useful to find corner # cases. clang-everything: CC=clang -clang-everything: CFLAGS+=-Weverything -Wno-disabled-macro-expansion -Wno-padded -Wno-covered-switch-default -Wno-gnu-zero-variadic-macro-arguments +clang-everything: CFLAGS+=-Weverything -Wno-disabled-macro-expansion -Wno-padded -Wno-covered-switch-default -Wno-gnu-zero-variadic-macro-arguments -Wno-declaration-after-statement clang-everything: all sanitisers: CFLAGS+=-fsanitize=address -fsanitize=undefined -fanalyzer @@ -41,11 +47,11 @@ install: all mkdir -p $(DESTDIR)$(bindir)/ $(INSTALL) -pt $(DESTDIR)$(bindir)/ $(EXECUTABLES) - $(INSTALL) -Dp -m 644 zcfan.service $(DESTDIR)$(prefix)/lib/systemd/system/zcfan.service + $(INSTALL) -Dp -m 644 $(SERVICE) $(DESTDIR)$(prefix)/lib/systemd/system/$(SERVICE) $(INSTALL) -Dp -m 644 zcfan.1 $(DESTDIR)$(mandir)/man1/zcfan.1 lint: clang-format -style=file --dry-run --Werror zcfan.c clean: - rm -f $(EXECUTABLES) + rm -f $(EXECUTABLES) $(SERVICE) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zcfan-1.3.0/README.md new/zcfan-1.4.0/README.md --- old/zcfan-1.3.0/README.md 2024-02-27 03:55:17.000000000 +0100 +++ new/zcfan-1.4.0/README.md 2024-12-18 01:36:27.000000000 +0100 @@ -26,18 +26,28 @@ If no trip temperature is reached, the fan will be turned off. +The fan will also only be reduced once the temperature is now at least 10C +below the trip temperature for the current fan state. This can be tuned with +the config parameter `temp_hysteresis`. + To override these defaults, you can place a file at `/etc/zcfan.conf` with -updated trip temperatures in degrees celsius. As an example: +updated trip temperatures in degrees celsius and/or fan levels. As an example: max_temp 85 med_temp 70 low_temp 55 + temp_hysteresis 20 + + max_level full-speed + med_level 4 + low_level 1 ### Hysteresis We will only reduce the fan level again once: -1. The temperature is now at least 10C below the trip point, and +1. The temperature is now at least `temp_hysteresis` Celsius (default 10C) + below the trip point, and 2. At least 3 seconds have elapsed since the initial trip. This avoids unnecessary fluctuations in fan speed. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zcfan-1.3.0/zcfan.1 new/zcfan-1.4.0/zcfan.1 --- old/zcfan-1.3.0/zcfan.1 2024-02-27 03:55:17.000000000 +0100 +++ new/zcfan-1.4.0/zcfan.1 2024-12-18 01:36:27.000000000 +0100 @@ -51,7 +51,9 @@ We will only reduce the fan level again once: .IP "1." 3 -The temperature is now at least 10C below the trip point, and +The temperature is now at least +.I temp_hysteresis +Celsius below the trip point (default 10C), and .IP "2." 3 At least 3 seconds have elapsed since the initial trip. .PP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zcfan-1.3.0/zcfan.c new/zcfan-1.4.0/zcfan.c --- old/zcfan-1.3.0/zcfan.c 2024-02-27 03:55:17.000000000 +0100 +++ new/zcfan-1.4.0/zcfan.c 2024-12-18 01:36:27.000000000 +0100 @@ -14,12 +14,15 @@ #define FAN_CONTROL_FILE "/proc/acpi/ibm/fan" #define TEMP_INVALID INT_MIN #define TEMP_MIN INT_MIN + 1 +#define NS_IN_SEC 1000000000L // 1 second in nanoseconds +#define THRESHOLD_NS 200000000 // 0.2 seconds #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) #define DEFAULT_WATCHDOG_SECS 120 #define S_DEFAULT_WATCHDOG_SECS STR(DEFAULT_WATCHDOG_SECS) +#define info(fmt, ...) fprintf(stderr, "[INF] " fmt, ##__VA_ARGS__) #define err(fmt, ...) fprintf(stderr, "[ERR] " fmt, ##__VA_ARGS__) #define max(x, y) ((x) > (y) ? (x) : (y)) #define expect(x) \ @@ -31,10 +34,13 @@ } \ } while (0) +#define CONFIG_MAX_STRLEN 15 +#define S_CONFIG_MAX_STRLEN STR(CONFIG_MAX_STRLEN) + /* Must be highest to lowest temp */ enum FanLevel { FAN_MAX, FAN_MED, FAN_LOW, FAN_OFF, FAN_INVALID }; struct Rule { - const char *tpacpi_level; + char tpacpi_level[CONFIG_MAX_STRLEN + 1]; int threshold; const char *name; }; @@ -47,7 +53,7 @@ static struct timespec last_watchdog_ping = {0, 0}; static time_t watchdog_secs = DEFAULT_WATCHDOG_SECS; -static const unsigned int fan_hysteresis = 10; +static int temp_hysteresis = 10; static const unsigned int tick_hysteresis = 3; static char output_buf[512]; static const struct Rule *current_rule = NULL; @@ -55,6 +61,11 @@ static int first_tick = 1; /* Stop running if errors are immediate */ static glob_t temp_files; +enum resume_state { + RESUME_NOT_DETECTED, + RESUME_DETECTED, +}; + static void exit_if_first_tick(void) { if (first_tick) { err("Quitting due to failure during first run\n"); @@ -62,6 +73,36 @@ } } +static int64_t timespec_diff_ns(const struct timespec *start, + const struct timespec *end) { + return ((int64_t)end->tv_sec - (int64_t)start->tv_sec) * NS_IN_SEC + + (end->tv_nsec - start->tv_nsec); +} + +static enum resume_state detect_suspend(void) { + static struct timespec monotonic_prev, boottime_prev; + struct timespec monotonic_now, boottime_now; + + expect(clock_gettime(CLOCK_MONOTONIC, &monotonic_now) == 0); + expect(clock_gettime(CLOCK_BOOTTIME, &boottime_now) == 0); + + if (monotonic_prev.tv_sec == 0 && monotonic_prev.tv_nsec == 0) { + monotonic_prev = monotonic_now; + boottime_prev = boottime_now; + return RESUME_NOT_DETECTED; + } + + int64_t delta_monotonic = timespec_diff_ns(&monotonic_prev, &monotonic_now); + int64_t delta_boottime = timespec_diff_ns(&boottime_prev, &boottime_now); + + monotonic_prev = monotonic_now; + boottime_prev = boottime_now; + + return delta_boottime > delta_monotonic + THRESHOLD_NS + ? RESUME_DETECTED + : RESUME_NOT_DETECTED; +} + static int glob_err_handler(const char *epath, int eerrno) { err("glob: %s: %s\n", epath, strerror(eerrno)); return 0; @@ -178,7 +219,7 @@ if (tick_penalty) { return 0; /* Must wait longer until able to move down levels */ } - temp_penalty = fan_hysteresis; + temp_penalty = temp_hysteresis; } if (rule->threshold < temp_penalty || @@ -203,15 +244,24 @@ static void maybe_ping_watchdog(void) { struct timespec now; + expect(current_rule); expect(clock_gettime(CLOCK_MONOTONIC, &now) == 0); + if (detect_suspend() == RESUME_DETECTED) { + // On resume, some models need a manual fan write again, or they will + // revert to "auto". + info("Clock jump detected, possible resume. Rewriting fan level\n"); + write_fan_level(current_rule->tpacpi_level); + } + if (now.tv_sec - last_watchdog_ping.tv_sec < (watchdog_secs - WATCHDOG_GRACE_PERIOD_SECS)) { return; } - expect(current_rule); /* Already set up on first run by set_fan_level */ - write_fan_level(current_rule->tpacpi_level); + // Transitioning from level 0 -> level 0 can cause a brief fan spinup on + // some models, so don't reset the timer by write_fan_level(). + write_watchdog_timeout(watchdog_secs); } #define CONFIG_PATH "/etc/zcfan.conf" @@ -225,6 +275,17 @@ } \ } while (0) +#define fscanf_str_for_key(f, pos, name, dest) \ + do { \ + char val[CONFIG_MAX_STRLEN + 1]; \ + if (fscanf(f, name " %" S_CONFIG_MAX_STRLEN "s ", val) == 1) { \ + strncpy(dest, val, CONFIG_MAX_STRLEN); \ + dest[CONFIG_MAX_STRLEN] = '\0'; \ + } else { \ + expect(fseek(f, pos, SEEK_SET) == 0); \ + } \ + } while (0) + static void get_config(void) { FILE *f; @@ -245,6 +306,10 @@ fscanf_int_for_key(f, pos, "med_temp", rules[FAN_MED].threshold); fscanf_int_for_key(f, pos, "low_temp", rules[FAN_LOW].threshold); fscanf_int_for_key(f, pos, "watchdog_secs", watchdog_secs); + fscanf_int_for_key(f, pos, "temp_hysteresis", temp_hysteresis); + fscanf_str_for_key(f, pos, "max_level", rules[FAN_MAX].tpacpi_level); + fscanf_str_for_key(f, pos, "med_level", rules[FAN_MED].tpacpi_level); + fscanf_str_for_key(f, pos, "low_level", rules[FAN_LOW].tpacpi_level); if (ftell(f) == pos) { while ((ch = fgetc(f)) != EOF && ch != '\n') {} } @@ -296,7 +361,8 @@ if (!full_speed_supported()) { err("level \"full-speed\" not supported, using level 7\n"); - rules[FAN_MAX].tpacpi_level = "7"; + strncpy(rules[FAN_MAX].tpacpi_level, "7", CONFIG_MAX_STRLEN); + rules[FAN_MAX].tpacpi_level[CONFIG_MAX_STRLEN] = '\0'; } write_watchdog_timeout(watchdog_secs); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zcfan-1.3.0/zcfan.service new/zcfan-1.4.0/zcfan.service --- old/zcfan-1.3.0/zcfan.service 2024-02-27 03:55:17.000000000 +0100 +++ new/zcfan-1.4.0/zcfan.service 1970-01-01 01:00:00.000000000 +0100 @@ -1,20 +0,0 @@ -[Unit] -Description=Zero-configuration fan control for ThinkPad - -[Service] -ExecStart=/usr/bin/zcfan -Restart=always -RestartSec=500ms - -MemoryDenyWriteExecute=yes -NoNewPrivileges=yes -ProtectControlGroups=yes -RestrictAddressFamilies= -RestrictRealtime=yes - -# We don't need to do any substantial clean up, so if something hangs it's -# going to stay that way. Just forcefully kill and get it over with. -TimeoutStopSec=2 - -[Install] -WantedBy=default.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zcfan-1.3.0/zcfan.service.in new/zcfan-1.4.0/zcfan.service.in --- old/zcfan-1.3.0/zcfan.service.in 1970-01-01 01:00:00.000000000 +0100 +++ new/zcfan-1.4.0/zcfan.service.in 2024-12-18 01:36:27.000000000 +0100 @@ -0,0 +1,21 @@ +[Unit] +Description=Zero-configuration fan control for ThinkPad +Conflicts=thinkfan.service + +[Service] +ExecStart=@bindir@/zcfan +Restart=always +RestartSec=500ms + +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +ProtectControlGroups=yes +RestrictAddressFamilies= +RestrictRealtime=yes + +# We don't need to do any substantial clean up, so if something hangs it's +# going to stay that way. Just forcefully kill and get it over with. +TimeoutStopSec=2 + +[Install] +WantedBy=default.target
