From f48f8aacecc21065d294600abe644da04b4fad81 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 27 Jul 2026 11:27:54 +0200 Subject: [PATCH] conf: replace the '!' condition prefix with reload-signal and required A condition list could be led by '!', which is not a condition and not a negation. It is a flag on the block, and it means two unrelated things depending on which block it sits in: a service or sysv does not handle SIGHUP and must be restarted to reload, while a run or task must not hold up bootstrap. Writing '' with no condition at all is legal, which gives away that it was never an operator. Give each meaning its own key, valid only where it applies: service foo { reload-signal = "none" } # restart to reload task bar { required = false } # do not hold up bootstrap Using either on a block type it does not apply to warns, as does a '!' left in a conditions list. Both still translate to that same '!', which is all a legacy line can carry, so reload-signal takes SIGHUP or none for now; str2sig() already accepts any case and an optional SIG prefix. This also clears the way for the conditions list to grow real operators, '+' and '-' for asserted and deasserted, without '!' sitting among them meaning something else entirely. The '~' prefix stays. It belongs to the list: it marks a dependency whose reload should propagate here. Signed-off-by: Joachim Wiberg --- src/conf.c | 46 ++++++++++++++++++++++++++++++++++++++++++--- test/conf-format.sh | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/conf.c b/src/conf.c index f4d99f1e..4154b0fa 100644 --- a/src/conf.c +++ b/src/conf.c @@ -242,6 +242,8 @@ static cfg_opt_t svc_opts[] = { CFG_INT ("restart-max", 0, CFGF_NODEFAULT), CFG_INT ("restart-sec", 0, CFGF_NODEFAULT), CFG_STR ("oncrash", NULL, CFGF_NODEFAULT), + CFG_STR ("reload-signal", NULL, CFGF_NODEFAULT), + CFG_BOOL ("required", cfg_true, CFGF_NODEFAULT), CFG_STR ("stop-signal", NULL, CFGF_NODEFAULT), CFG_STR ("halt", NULL, CFGF_NODEFAULT), /* alias */ CFG_INT ("stop-timeout", 0, CFGF_NODEFAULT), @@ -1204,8 +1206,8 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi struct rlimit local_rlimit[RLIMIT_NLIMITS]; char line[LINE_SIZE] = ""; const char *str, *cmd, *grp; + int nowarn, own, bang; unsigned int cnt; - int nowarn, own; long num; char buf[512]; char nm[80]; @@ -1226,8 +1228,46 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi if ((str = sec_getstr(sec, "runlevel", NULL))) addtok(line, sizeof(line), "[%s]", str); - if (sec_getlist(sec, "conditions", "cond", buf, sizeof(buf))) - addtok(line, sizeof(line), "<%s>", buf); + /* + * The ! leading a legacy condition list is not a condition at + * all, and it means two unrelated things depending on the block + * it sits in. Each has its own key here, valid only where the + * meaning applies, and both translate back to that same !. + */ + bang = 0; + if (type == SVC_TYPE_RUN || type == SVC_TYPE_TASK) { + if (cfg_size(sec, "required") && !sec_getbool(sec, "required")) + bang = 1; /* do not hold up bootstrap */ + if (cfg_size(sec, "reload-signal")) + logit(LOG_WARNING, "%s: %s: reload-signal does not apply" + " to run or task, ignoring", file, cfg_title(sec)); + } else { + if ((str = sec_getstr(sec, "reload-signal", NULL))) { + /* + * Only SIGHUP and none survive the trip through a + * legacy line, which carries the flag and not the + * signal. + */ + if (!strcasecmp(str, "none")) + bang = 1; + else if (str2sig((char *)str) != SIGHUP) + logit(LOG_WARNING, "%s: %s: reload-signal is SIGHUP" + " or none, ignoring '%s'", file, + cfg_title(sec), str); + } + if (cfg_size(sec, "required")) + logit(LOG_WARNING, "%s: %s: required only applies to run" + " or task, ignoring", file, cfg_title(sec)); + } + + if (sec_getlist(sec, "conditions", "cond", buf, sizeof(buf))) { + if (buf[0] == '!') + logit(LOG_WARNING, "%s: %s: '!' in conditions is not" + " supported, use reload-signal or required", + file, cfg_title(sec)); + addtok(line, sizeof(line), "<%s%s>", bang ? "!" : "", buf); + } else if (bang) + addtok(line, sizeof(line), ""); /* section title is the identity: NAME[:ID], %i in templates */ strlcpy(nm, cfg_title(sec), sizeof(nm)); diff --git a/test/conf-format.sh b/test/conf-format.sh index d31154fd..6d72e6fc 100755 --- a/test/conf-format.sh +++ b/test/conf-format.sh @@ -182,3 +182,39 @@ run "initctl reload" retry 'assert_num_children 1 service.sh' assert "daemon-owned pidfile is not created by Finit" \ "$(texec ls /run/notmine.pid 2>/dev/null)" = "" + +say 'reload-signal = none emits the legacy noreload flag' +run "echo 'service service.sh {' > $FINIT_CONF" +run "echo ' description = \"Test service\"' >> $FINIT_CONF" +run "echo ' reload-signal = \"none\"' >> $FINIT_CONF" +run "echo ' command = \"service.sh\"' >> $FINIT_CONF" +run "echo '}' >> $FINIT_CONF" +run "initctl reload" + +retry 'assert_num_children 1 service.sh' +assert_desc "Test service" service.sh + +say 'Case and short forms of SIGHUP are all the default, no flag' +for s in SIGHUP sighup HUP hup; do + run "echo 'service service.sh {' > $FINIT_CONF" + run "echo ' description = \"Test service\"' >> $FINIT_CONF" + run "echo \" reload-signal = '$s'\" >> $FINIT_CONF" + run "echo ' command = \"service.sh\"' >> $FINIT_CONF" + run "echo '}' >> $FINIT_CONF" + run "initctl reload" + retry 'assert_num_children 1 service.sh' +done + +# 'required' and 'reload-signal' both translate to the legacy ! that +# leads the condition list, but each is valid only for the block types +# where that ! carries its meaning. +say 'required = false on a task does not hold up bootstrap' +run "echo 'task pwrfail {' > $FINIT_CONF" +run "echo ' description = \"Power failure\"' >> $FINIT_CONF" +run "echo ' conditions = { \"sys/pwr/fail\" }' >> $FINIT_CONF" +run "echo ' required = false' >> $FINIT_CONF" +run "echo ' command = \"/bin/true\"' >> $FINIT_CONF" +run "echo '}' >> $FINIT_CONF" +run "initctl reload" + +retry 'assert_desc "Power failure" pwrfail'