Compare commits

..
14 Commits
Author SHA1 Message Date
Joachim Wiberg 8451c4087a Merge pull request #522 from firasuke/master
doc: list libcap as a dependency
2026-11-14 12:14:04 +01:00
Firas Khana e280569941 doc: list libcap as a dependency 2026-11-14 13:44:58 +03:00
Joachim Wiberg f15e95d1cc service: don't take a command named like an option for one
A run/task/service whose command starts like an option without a ':'
lost its command, e.g. the example in finit.conf(5):

    run [2345] @joe:users logger "Hello world"

was registered as the log option, running "Hello world".  Same for
pidof, pidproxy, cgroupfs-mount, and in block format too.

The option parser matches with MATCH_CMD(), a prefix match, also for
the options with no trailing ':' to end the prefix: log, pid, cgroup,
respawn, nowarn, and the rest.

Match these against the whole token, allowing only the separator that
starts a value: "log:", "pid:", "cgroup." and "cgroup:".

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-13 16:00:25 +01:00
Joachim Wiberg 34a0a4b1ff conf: pass -- in a block command to the program
A command that takes -- as an argument lost it, and everything after
it, to the description:

    service name {
        command     = "cmd -- arg"
        description = "a cmd"
    }

ran `cmd` with description `arg`.

The block translator builds a legacy one-liner, appending the
description as "-- text", and service_register() splits that line at
the first "-- ", here the one in the command.

Split the description off only for legacy one-liners.  Blocks now hand
it to the new service_register_desc() as an argument.

Fixes #512

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-13 15:56:30 +01:00
Paweł c03059a9a0 keventd: don't publish whole-disk by-id links for partitions
disk_serial_id() falls back to <disk>/../serial so a partition got a
by-id link named after its parent disk's serial, clobbering the link
created for the whole disk.

(cherry picked from commit 4b2f8c89c5)
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-13 15:06:08 +01:00
Joachim Wiberg 297d2de1da Merge pull request #511 from zhangxiaohui1/fix/remain-task-control
service: preserve explicit stop for remain tasks
2026-11-12 21:06:47 +01:00
Joachim Wiberg 3acf1cb7b1 Merge pull request #517 from vazub/hwdb-parse-fix
keventd: fix hwdb lookups that never match
2026-11-12 19:25:38 +01:00
Joachim Wiberg eb9fb83011 Merge pull request #516 from vazub/symlink-fix
keventd: fix dangling and misnamed /dev/input/by-id links
2026-11-12 19:24:02 +01:00
Joachim Wiberg b6f0d5314b Merge pull request #521 from firasuke/firasuke-patch-1
Add conditional include for sm.h based on AUTO_RELOAD
2026-11-12 19:22:53 +01:00
Firas Khana 90512b07a0 Add conditional include for sm.h based on AUTO_RELOAD 2026-11-06 23:09:13 +03:00
Firas Khana d313b1fd74 Add conditional include for sm.h based on AUTO_RELOAD 2026-11-06 23:04:57 +03:00
Basil Zubko 439cab0558 keventd: fix hwdb parsing issues 2026-10-31 20:42:07 +01:00
Basil Zubko 98b2effb52 keventd: fix dev symlinks 2026-10-30 12:30:40 +01:00
zhangxiaohui 3a18ca4a30 service: preserve explicit stop for remain tasks
A completed remain task unconditionally cleared its user stop block
after running its post script.  Since it was still valid in the current
runlevel, it immediately started again, making initctl stop ineffective.

Keep explicit stops blocked, distinguish automatic and requested
restarts, and route initctl restart through teardown before rerunning the
task.  Cover stop, start, restart, reload, and runlevel transitions with
a regression test.

Signed-off-by: zhangxiaohui <xiaohui.zhang@wbstar.com>
2026-10-21 19:03:46 +08:00
13 changed files with 415 additions and 89 deletions

No files matched your search

+6
View File
@@ -153,6 +153,12 @@ All relevant changes are documented in this file.
### Fixes ### Fixes
- Fix #512: `--` in the `command` of a block-format .conf, it was taken as the
start of the description, so `command = "cmd -- arg"` ran `cmd` with
description `arg`. Found by Alexandru Paniș
- Fix a command whose name starts like a service option, e.g. `logger` or
`pidof`. It was taken as the `log` or `pid` option and the next argument
ran as the command, which also broke the `logger` example in finit.conf(5)
- Reject a .conf file that declares the same block title twice. The title is - Reject a .conf file that declares the same block title twice. The title is
the service identity and libConfuse merges two sections sharing one, so the the service identity and libConfuse merges two sections sharing one, so the
file loaded as a single service holding a mix of both declarations, file loaded as a single service holding a mix of both declarations,
+10
View File
@@ -15,6 +15,10 @@ and `/dev/disk/by-label/` symlinks after. keventd is enabled by
default, so this is a hard requirement unless you build with default, so this is a hard requirement unless you build with
`--without-keventd`. `--without-keventd`.
Linux capabilities support for services is also built by default. It needs
[libcap][] (-lcap); the build falls back to no capabilities support when that
library is missing, or with `--disable-libcap`.
PAM session support for services is built by default. It needs PAM session support for services is built by default. It needs
[Linux-PAM][] (-lpam); the build falls back to no PAM support when that [Linux-PAM][] (-lpam); the build falls back to no PAM support when that
library is missing. A static build, `--enable-static`, disables PAM library is missing. A static build, `--enable-static`, disables PAM
@@ -37,6 +41,7 @@ version of the above mentioned libraries. Currently required versions:
- libuEv v2.2.0, or later - libuEv v2.2.0, or later
- libConfuse v3.3, or later - libConfuse v3.3, or later
- libblkid, any version, when keventd is enabled - libblkid, any version, when keventd is enabled
- libcap, any version, unless `--disable-libcap` is given
Configure Configure
@@ -59,6 +64,10 @@ Below are a few of the main switches to configure:
built-ins (.o files) and all external libraries, except the C library built-ins (.o files) and all external libraries, except the C library
will be linked statically. will be linked statically.
* `--disable-libcap`: Opt out of Finit's built-in Linux capabilities support,
enabled by default. Needs libcap, and falls back to disabled when it is
missing. See [Linux Capabilities](config/capabilities.md)
* `--disable-pam`: Opt out of Finit's built-in PAM session support, * `--disable-pam`: Opt out of Finit's built-in PAM session support,
enabled by default, which lets a service declare `pam = "name"` and enabled by default, which lets a service declare `pam = "name"` and
run inside a session set up from `/etc/pam.d/name`. Needs libpam, run inside a session set up from `/etc/pam.d/name`. Needs libpam,
@@ -298,4 +307,5 @@ it only for debugging start up issues when Finit crashes.
[libite]: https://github.com/troglobit/libite [libite]: https://github.com/troglobit/libite
[libConfuse]: https://github.com/libconfuse/libconfuse [libConfuse]: https://github.com/libconfuse/libconfuse
[util-linux]: https://github.com/util-linux/util-linux [util-linux]: https://github.com/util-linux/util-linux
[libcap]: https://sites.google.com/site/fullycapable/
[Linux-PAM]: https://github.com/linux-pam/linux-pam [Linux-PAM]: https://github.com/linux-pam/linux-pam
+3 -2
View File
@@ -169,8 +169,9 @@ ps -o user,pid,cmd -p $(pidof nginx)
- Linux kernel 4.3+ (for ambient capabilities support) - Linux kernel 4.3+ (for ambient capabilities support)
- libcap library installed - libcap library installed
- Finit built with `--enable-libcap`, otherwise a `capabilities` list is - Finit built with libcap support (enabled by default unless `--disable-libcap`
ignored, with a warning is given or libcap was missing at build time), otherwise a `capabilities` list
is ignored, with a warning
## Limitations ## Limitations
+76 -21
View File
@@ -81,6 +81,13 @@ static int builtin_kmod(struct uevent *ev, int argc, char **argv)
/* ---- hwdb -------------------------------------------------------- */ /* ---- hwdb -------------------------------------------------------- */
/*
* Longest lookup key. A key built by the stock name-based evdev rule
* (name, phys, ev and the DMI modalias) is 280 to 300 bytes with typical
* DMI strings, and 250 to 265 bytes for the plain name rule.
*/
#define HWDB_KEY_MAX 1024
/* /*
* Match a single text hwdb file against the lookup key. * Match a single text hwdb file against the lookup key.
* Text format: match lines at column 0, property lines indented. * Text format: match lines at column 0, property lines indented.
@@ -106,7 +113,11 @@ static void hwdb_match_file(const char *path, const char *lookup,
while (fgets(line, sizeof(line), fp)) { while (fgets(line, sizeof(line), fp)) {
line[strcspn(line, "\n")] = '\0'; line[strcspn(line, "\n")] = '\0';
if (!line[0] || line[0] == '#') { /* Comments do not end the record being read */
if (line[0] == '#')
continue;
if (!line[0]) {
matched = 0; matched = 0;
in_props = 0; in_props = 0;
continue; continue;
@@ -145,9 +156,11 @@ static void hwdb_match_file(const char *path, const char *lookup,
/* /*
* Build the hwdb lookup key for input devices. * Build the hwdb lookup key for input devices.
* *
* udev hwdb files use the format: * The key is the modalias of the input device, which the rule's
* evdev:input:b{BUS}v{VENDOR}p{PRODUCT}e{VERSION}* * --lookup-prefix (evdev:, id-input:modalias:) is put in front of:
* where all fields are 4-digit uppercase hex. * input:b{BUS}v{VENDOR}p{PRODUCT}e{VERSION}-e{CAPABILITIES...}
* where the IDs are 4-digit uppercase hex. If the device has no modalias
* attribute, the part up to the version is built from the id/ files.
* *
* The id/ files live under the input device directory. For eventN nodes * The id/ files live under the input device directory. For eventN nodes
* (devpath ends in .../inputN/eventN) we strip the last component first. * (devpath ends in .../inputN/eventN) we strip the last component first.
@@ -156,7 +169,7 @@ static void hwdb_input_key(const struct uevent *ev, char *key, size_t len)
{ {
char base[PATH_MAX]; char base[PATH_MAX];
char path[PATH_MAX]; char path[PATH_MAX];
char bus_s[8], vendor_s[8], product_s[8], version_s[8]; char bus_s[8] = "", vendor_s[8] = "", product_s[8] = "", version_s[8] = "";
if (!ev->devpath) if (!ev->devpath)
return; return;
@@ -180,11 +193,28 @@ static void hwdb_input_key(const struct uevent *ev, char *key, size_t len)
snprintf(path, sizeof(path), "/sys%s", ev->devpath); snprintf(path, sizeof(path), "/sys%s", ev->devpath);
} }
snprintf(base, sizeof(base), "%s/id/vendor", path); sysfs_read_file(base, vendor_s, sizeof(vendor_s)); /*
snprintf(base, sizeof(base), "%s/id/product", path); sysfs_read_file(base, product_s, sizeof(product_s)); * Prefer the modalias of the input device. Unlike the id/ files it
snprintf(base, sizeof(base), "%s/id/version", path); sysfs_read_file(base, version_s, sizeof(version_s)); * carries the capability bitmaps (-e0,1,2,4,k110,...) that some
* entries match on.
*/
snprintf(base, sizeof(base), "%s/modalias", path);
if (!sysfs_read_file(base, key, len) && !strncmp(key, "input:", 6))
return;
key[0] = '\0';
snprintf(key, len, "evdev:input:b%04Xv%04Xp%04Xe%04X*", /* Otherwise build it from the id/ files; without a full set there is no key. */
snprintf(base, sizeof(base), "%s/id/vendor", path);
if (sysfs_read_file(base, vendor_s, sizeof(vendor_s)) < 0)
return;
snprintf(base, sizeof(base), "%s/id/product", path);
if (sysfs_read_file(base, product_s, sizeof(product_s)) < 0)
return;
snprintf(base, sizeof(base), "%s/id/version", path);
if (sysfs_read_file(base, version_s, sizeof(version_s)) < 0)
return;
snprintf(key, len, "input:b%04Xv%04Xp%04Xe%04X*",
(unsigned)strtoul(bus_s, NULL, 16), (unsigned)strtoul(bus_s, NULL, 16),
(unsigned)strtoul(vendor_s, NULL, 16), (unsigned)strtoul(vendor_s, NULL, 16),
(unsigned)strtoul(product_s, NULL, 16), (unsigned)strtoul(product_s, NULL, 16),
@@ -265,24 +295,48 @@ static void hwdb_scan(const char *key, struct uevent *ev)
} }
} }
/* Insert prefix in front of key, in place; -1 if the result does not fit. */
static int hwdb_prepend(char *key, size_t len, const char *prefix)
{
size_t plen = strlen(prefix), klen = strlen(key);
if (plen + klen >= len)
return -1;
memmove(key + plen, key, klen + 1);
memcpy(key, prefix, plen);
return 0;
}
static int builtin_hwdb(struct uevent *ev, int argc, char **argv) static int builtin_hwdb(struct uevent *ev, int argc, char **argv)
{ {
const char *subsys = NULL; const char *subsys = NULL;
const char *prefix = NULL; const char *prefix = NULL;
char key[256] = ""; const char *explicit_key = NULL;
char key[HWDB_KEY_MAX] = "";
int i; int i;
/* Parse --subsystem=X and --lookup-prefix=Y */ /*
* Parse --subsystem=X and --lookup-prefix=Y. The first argument
* that is not an option is the lookup key itself, which is how the
* stock udev rules use the builtin:
*
* IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'"
*/
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
if (!strncmp(argv[i], "--subsystem=", 12)) if (!strncmp(argv[i], "--subsystem=", 12))
subsys = argv[i] + 12; subsys = argv[i] + 12;
else if (!strncmp(argv[i], "--lookup-prefix=", 16)) else if (!strncmp(argv[i], "--lookup-prefix=", 16))
prefix = argv[i] + 16; prefix = argv[i] + 16;
else if (argv[i][0] != '-' && !explicit_key)
explicit_key = argv[i];
} }
if (!subsys) if (!subsys)
subsys = ev->subsystem; subsys = ev->subsystem;
if (subsys && !strcmp(subsys, "input")) { if (explicit_key) {
if (snprintf(key, sizeof(key), "%s", explicit_key) >= (int)sizeof(key))
goto too_long;
} else if (subsys && !strcmp(subsys, "input")) {
hwdb_input_key(ev, key, sizeof(key)); hwdb_input_key(ev, key, sizeof(key));
} else if (subsys && (!strcmp(subsys, "usb") || } else if (subsys && (!strcmp(subsys, "usb") ||
!strcmp(subsys, "usb_device"))) { !strcmp(subsys, "usb_device"))) {
@@ -290,29 +344,30 @@ static int builtin_hwdb(struct uevent *ev, int argc, char **argv)
} }
/* Fall back to MODALIAS / ev->modalias for pci, platform, etc. */ /* Fall back to MODALIAS / ev->modalias for pci, platform, etc. */
if (!key[0]) { if (!key[0] && !explicit_key) {
const char *m = uevent_getenv(ev, "MODALIAS"); const char *m = uevent_getenv(ev, "MODALIAS");
if (!m) if (!m)
m = ev->modalias; m = ev->modalias;
if (m) if (m && snprintf(key, sizeof(key), "%s", m) >= (int)sizeof(key))
snprintf(key, sizeof(key), "%s", m); goto too_long;
} }
if (!key[0]) if (!key[0])
return 0; return 0;
/* --lookup-prefix scopes the match by prepending to the key. */ /* --lookup-prefix scopes the match by prepending to the key. */
if (prefix && *prefix) { if (prefix && *prefix && hwdb_prepend(key, sizeof(key), prefix))
char tmp[sizeof(key)]; goto too_long;
snprintf(tmp, sizeof(tmp), "%s%s", prefix, key);
snprintf(key, sizeof(key), "%s", tmp);
}
logit(LOG_DEBUG, "rules: hwdb lookup: %s", key); logit(LOG_DEBUG, "rules: hwdb lookup: %s", key);
hwdb_scan(key, ev); hwdb_scan(key, ev);
return 0; return 0;
too_long:
logit(LOG_WARNING, "rules: hwdb key too long (max %d bytes), lookup skipped",
HWDB_KEY_MAX - 1);
return 0;
} }
/* ---- path_id ----------------------------------------------------- */ /* ---- path_id ----------------------------------------------------- */
+34 -3
View File
@@ -501,6 +501,33 @@ void rules_free(struct rule_list *list)
/* ----- variable substitution ------------------------------------------- */ /* ----- variable substitution ------------------------------------------- */
/*
* Syspath of the parent device selected by the last successful
* KERNELS/SUBSYSTEMS/DRIVERS/ATTRS== match of the rule being evaluated.
* As in udev, $attr{} and %s{} fall back to this parent when the event
* device has no such attribute itself; the stock rules rely on it:
*
* ATTRS{bInterfaceNumber}=="?*", SYMLINK+="...-if$attr{bInterfaceNumber}-..."
*
* Cleared at the start of every rule and again once it has been applied.
*/
static char matched_parent[PATH_MAX];
/* Read an attribute for $attr{}/%s{}: the event device first, then the matched parent */
static int subst_read_attr(const struct uevent *ev, const char *attr,
char *buf, size_t len)
{
char path[PATH_MAX];
if (ev->devpath && !sysfs_read_attr(ev->devpath, attr, buf, len))
return 0;
if (!matched_parent[0])
return -1;
snprintf(path, sizeof(path), "%s/%s", matched_parent, attr);
return sysfs_read_file(path, buf, len);
}
static void append_str(char *buf, size_t *pos, size_t bufsz, const char *s) static void append_str(char *buf, size_t *pos, size_t bufsz, const char *s)
{ {
size_t l; size_t l;
@@ -604,8 +631,7 @@ static void subst_value(const char *src, char *buf, size_t bufsz,
al = sizeof(attr) - 1; al = sizeof(attr) - 1;
memcpy(attr, as, al); memcpy(attr, as, al);
attr[al] = 0; attr[al] = 0;
if (!sysfs_read_attr(ev->devpath, attr, if (!subst_read_attr(ev, attr, tmp, sizeof(tmp)))
tmp, sizeof(tmp)))
append_str(buf, &pos, bufsz, tmp); append_str(buf, &pos, bufsz, tmp);
p = ae + 1; p = ae + 1;
} else { } else {
@@ -681,7 +707,7 @@ static void subst_value(const char *src, char *buf, size_t bufsz,
al = sizeof(attr) - 1; al = sizeof(attr) - 1;
memcpy(attr, as, al); memcpy(attr, as, al);
attr[al] = 0; attr[al] = 0;
if (!sysfs_read_attr(ev->devpath, attr, tmp, sizeof(tmp))) if (!subst_read_attr(ev, attr, tmp, sizeof(tmp)))
append_str(buf, &pos, bufsz, tmp); append_str(buf, &pos, bufsz, tmp);
p = ae + 1; p = ae + 1;
} else { } else {
@@ -908,6 +934,8 @@ static int parent_cb(const char *syspath, void *data)
if (pattern_match(k->value, subject, k->pat_type)) { if (pattern_match(k->value, subject, k->pat_type)) {
ctx->found = 1; ctx->found = 1;
if (k->op == OP_MATCH_EQ)
snprintf(matched_parent, sizeof(matched_parent), "%s", syspath);
return 0; /* stop walking */ return 0; /* stop walking */
} }
return 1; /* keep walking */ return 1; /* keep walking */
@@ -1101,6 +1129,8 @@ static int rule_matches(const struct rule *r, struct uevent *ev)
{ {
int i; int i;
matched_parent[0] = 0;
for (i = 0; i < r->nkeys; i++) { for (i = 0; i < r->nkeys; i++) {
const struct rule_key *k = &r->keys[i]; const struct rule_key *k = &r->keys[i];
@@ -1627,6 +1657,7 @@ int rules_apply(struct rule_list *list, struct uevent *ev)
uevent_action_str(ev->action), ev->devpath ?: ""); uevent_action_str(ev->action), ev->devpath ?: "");
exec_rule(r, ev); exec_rule(r, ev);
matched_parent[0] = 0;
/* GOTO takes effect after this rule's assignments are applied */ /* GOTO takes effect after this rule's assignments are applied */
goto_label = NULL; goto_label = NULL;
+76 -18
View File
@@ -507,6 +507,62 @@ static struct devrule *find_rule(struct uevent *ev)
return &devrules[NELEMS(devrules) - 1]; return &devrules[NELEMS(devrules) - 1];
} }
/*
* Build the relative target of a symlink at /dev/<link> that points at the
* device node /dev/<node>, e.g.
*
* link "input/by-id/usb-X-event-kbd", node "input/event17" -> "../event17"
* link "disk/by-id/ata-X", node "sda" -> "../../sda"
*
* Both arguments are relative to /dev. The directory components the link
* and the node have in common are dropped, then one "../" is emitted for
* each directory the link still has to climb out of.
*/
static int symlink_relative_target(const char *link, const char *node,
char *buf, size_t len)
{
const char *l = link, *n = node;
size_t off = 0;
int ret;
while (*l == '/')
l++;
while (*n == '/')
n++;
/* Skip leading directories shared by both paths */
for (;;) {
const char *ls = strchr(l, '/');
const char *ns = strchr(n, '/');
if (!ls || !ns || ls - l != ns - n || strncmp(l, n, ls - l))
break;
l = ls + 1;
n = ns + 1;
while (*l == '/')
l++;
while (*n == '/')
n++;
}
buf[0] = 0;
for (; (l = strchr(l, '/')); l++) {
while (l[1] == '/')
l++;
if (!l[1])
break;
ret = snprintf(buf + off, len - off, "../");
if (ret < 0 || (size_t)ret >= len - off)
return -1;
off += ret;
}
ret = snprintf(buf + off, len - off, "%s", n);
if (ret < 0 || (size_t)ret >= len - off)
return -1;
return 0;
}
/* /*
* Create device node in /dev. * Create device node in /dev.
*/ */
@@ -792,7 +848,7 @@ static int disk_path_id(struct uevent *ev, char *id, size_t len)
static int symlink_add_disk(struct uevent *ev) static int symlink_add_disk(struct uevent *ev)
{ {
char target[PATH_MAX], link[PATH_MAX], id[256]; char target[PATH_MAX], link[PATH_MAX], id[256];
const char *name; const char *name, *devtype;
if (!ev->devname) if (!ev->devname)
return -1; return -1;
@@ -801,13 +857,22 @@ static int symlink_add_disk(struct uevent *ev)
name = strrchr(ev->devname, '/'); name = strrchr(ev->devname, '/');
name = name ? name + 1 : ev->devname; name = name ? name + 1 : ev->devname;
/* by-id: serial-based identifier */ /*
* by-id: serial-based identifier.
* Partitions are skipped, as their serial lives on the parent disk and would
* clobber its by-id link, partitions get theirs from the udev rules instead.
*/
devtype = uevent_getenv(ev, "DEVTYPE");
if (devtype && !strcmp(devtype, "partition"))
goto path;
if (!disk_serial_id(ev, id, sizeof(id))) { if (!disk_serial_id(ev, id, sizeof(id))) {
snprintf(target, sizeof(target), "../../%s", name); snprintf(target, sizeof(target), "../../%s", name);
snprintf(link, sizeof(link), "/dev/disk/by-id/%s", id); snprintf(link, sizeof(link), "/dev/disk/by-id/%s", id);
symlink_create(target, link, ev->devpath); symlink_create(target, link, ev->devpath);
} }
path:
/* by-path: topology-based identifier */ /* by-path: topology-based identifier */
if (!disk_path_id(ev, id, sizeof(id))) { if (!disk_path_id(ev, id, sizeof(id))) {
snprintf(target, sizeof(target), "../../%s", name); snprintf(target, sizeof(target), "../../%s", name);
@@ -903,9 +968,6 @@ int symlink_add(struct uevent *ev)
for (i = 0; i < ev->applied.nsymlinks; i++) { for (i = 0; i < ev->applied.nsymlinks; i++) {
const char *devname, *sl; const char *devname, *sl;
char link[PATH_MAX], target[PATH_MAX]; char link[PATH_MAX], target[PATH_MAX];
size_t toff = 0;
int depth = 0;
const char *p;
sl = ev->applied.symlinks[i]; sl = ev->applied.symlinks[i];
if (!sl || !*sl) if (!sl || !*sl)
@@ -915,19 +977,15 @@ int symlink_add(struct uevent *ev)
if (!devname) if (!devname)
continue; continue;
/* basename of devname for the symlink target */ /*
const char *base = strrchr(devname, '/'); * The node lives at /dev/<devname>, which is not necessarily
base = base ? base + 1 : devname; * directly in /dev (input/event17, dri/card0, ...), so the
* target must be computed relative to the link's directory.
/* Count '/' in symlink path to determine relative depth */ */
for (p = sl; *p; p++) if (symlink_relative_target(sl, devname, target, sizeof(target))) {
if (*p == '/') logit(LOG_WARNING, "Symlink %s -> %s: path too long", sl, devname);
depth++; continue;
}
target[0] = 0;
for (int d = 0; d < depth; d++)
toff += snprintf(target + toff, sizeof(target) - toff, "../");
snprintf(target + toff, sizeof(target) - toff, "%s", base);
snprintf(link, sizeof(link), "/dev/%s", sl); snprintf(link, sizeof(link), "/dev/%s", sl);
symlink_create(target, link, ev->devpath ?: ""); symlink_create(target, link, ev->devpath ?: "");
+3
View File
@@ -42,6 +42,9 @@
#include "iwatch.h" #include "iwatch.h"
#include "conf.h" #include "conf.h"
#include "service.h" #include "service.h"
#ifdef AUTO_RELOAD
#include "sm.h"
#endif
#include "log.h" #include "log.h"
#include "util.h" #include "util.h"
+8 -7
View File
@@ -67,6 +67,9 @@
#include "legacy.h" #include "legacy.h"
#include "private.h" #include "private.h"
#include "service.h" #include "service.h"
#ifdef AUTO_RELOAD
#include "sm.h"
#endif
#include "helpers.h" #include "helpers.h"
#include "util.h" #include "util.h"
@@ -1123,7 +1126,7 @@ static int sec_getbool(cfg_t *sec, const char *key, const char *alias)
* The line-based format could only spell this as one stanza per * The line-based format could only spell this as one stanza per
* candidate, relying on nowarn to skip the ones that were not * candidate, relying on nowarn to skip the ones that were not
* installed. A candidate not being there is not an error here, so * installed. A candidate not being there is not an error here, so
* only the winner is looked up again by service_register(). * only the winner is looked up again by service_register_desc().
* *
* A leading '-' still says a missing binary is expected, cf. systemd * A leading '-' still says a missing binary is expected, cf. systemd
* ExecStart=-. * ExecStart=-.
@@ -1440,7 +1443,7 @@ static void provides_translate(cfg_t *sec, svc_t *svc, char *file)
/* /*
* These have no legacy token, they are validated by service_set_dir() * These have no legacy token, they are validated by service_set_dir()
* and stored directly on the registered svc. Empty means unset, * and stored directly on the registered svc. Empty means unset,
* service_register() has already cleared the fields. * service_register_desc() has already cleared the fields.
*/ */
static void dirs_translate(cfg_t *sec, svc_t *svc, char *file) static void dirs_translate(cfg_t *sec, svc_t *svc, char *file)
{ {
@@ -1725,11 +1728,9 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi
/* the command and its arguments, verbatim */ /* the command and its arguments, verbatim */
addtok(line, sizeof(line), "%s", cmd); addtok(line, sizeof(line), "%s", cmd);
if ((str = sec_getstr(sec, "description", "desc"))) str = sec_getstr(sec, "description", "desc");
addtok(line, sizeof(line), "-- %s", str);
dbg("translated: %s", line); dbg("translated: %s", line);
svc = service_register(type, line, rlimit, file); svc = service_register_desc(type, line, str, rlimit, file);
if (!svc) if (!svc)
return; return;
@@ -1804,7 +1805,7 @@ static void tty_translate(cfg_t *sec, struct rlimit rlimit[], char *file)
addtok(line, sizeof(line), "%s", str); addtok(line, sizeof(line), "%s", str);
dbg("translated: tty %s", line); dbg("translated: tty %s", line);
service_register(SVC_TYPE_TTY, line, rlimit, file); service_register_desc(SVC_TYPE_TTY, line, NULL, rlimit, file);
} }
/* /*
+95 -37
View File
@@ -1599,6 +1599,15 @@ int service_restart_now(svc_t *svc)
if (!svc) if (!svc)
return 1; return 1;
if (svc_is_runtask(svc) && svc_is_remain(svc) &&
svc->state == SVC_DONE_STATE) {
service_timeout_cancel(svc);
svc_restarting(svc);
service_step(svc);
return 0;
}
if (!svc_is_running(svc)) if (!svc_is_running(svc))
return service_start_now(svc); return service_start_now(svc);
@@ -1803,6 +1812,24 @@ static char *parse_args(char **line)
return NULL; return NULL;
} }
/*
* Match an option that takes a value, @opt on its own or followed by
* one of the characters in @sep, e.g. "log" and "log:/path". Returns
* the value, or NULL, so a command like logger or pidof is not taken
* for an option.
*/
static char *is_opt(char *tok, const char *opt, const char *sep)
{
size_t len = strlen(opt);
if (strncasecmp(tok, opt, len))
return NULL;
if (tok[len] && !strchr(sep, tok[len]))
return NULL;
return &tok[len];
}
/* /*
* log:/path/to/logfile,priority:facility.level,tag:ident * log:/path/to/logfile,priority:facility.level,tag:ident
*/ */
@@ -2147,9 +2174,10 @@ static void parse_cmdline_args(svc_t *svc, char *cmd, char **args)
} }
/** /**
* service_register - Register service, task or run commands * service_register_desc - Register service, task or run commands
* @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2) * @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2)
* @cfg: Configuration, complete command, with -- for description text * @cfg: Configuration, complete command, without description text
* @desc: Description text, or %NULL
* @rlimit: Limits for this service/task/run, may be global limits * @rlimit: Limits for this service/task/run, may be global limits
* @file: The file name service was loaded from * @file: The file name service was loaded from
* *
@@ -2207,9 +2235,10 @@ static void parse_cmdline_args(svc_t *svc, char *cmd, char **args)
* skipped on purpose -- conditional loading, bootstrap over, nowarn -- * skipped on purpose -- conditional loading, bootstrap over, nowarn --
* also returns %NULL, with @errno zero. * also returns %NULL, with @errno zero.
*/ */
svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file) svc_t *service_register_desc(int type, char *cfg, const char *desc,
struct rlimit rlimit[], char *file)
{ {
char *cmd, *desc, *runlevels = NULL, *cond = NULL; char *cmd, *runlevels = NULL, *cond = NULL;
char *username = NULL, *log = NULL, *pid = NULL; char *username = NULL, *log = NULL, *pid = NULL;
char *name = NULL, *halt = NULL, *delay = NULL; char *name = NULL, *halt = NULL, *delay = NULL;
char *id = NULL, *env = NULL, *cgroup = NULL; char *id = NULL, *env = NULL, *cgroup = NULL;
@@ -2245,24 +2274,6 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
return NULL; return NULL;
} }
desc = strstr(line, "-- ");
if (desc) {
*desc = 0;
desc += 3;
while (*desc && isblank(*desc))
desc++;
} else {
int pos;
/* Find "--\n" to denote empty/no description */
pos = (int)strlen(line) - 2;
if (pos > 0 && !strcmp(&line[pos], "--")) {
line[pos] = 0;
desc = &line[pos];
}
}
args = line; args = line;
cmd = parse_args(&args); cmd = parse_args(&args);
if (!cmd) { if (!cmd) {
@@ -2272,6 +2283,10 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
return NULL; return NULL;
} }
/*
* Options ending in ':' can be prefix matched, the rest must match
* the whole token or a command like logger is taken for one.
*/
while (cmd) { while (cmd) {
char *arg; char *arg;
@@ -2286,19 +2301,19 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
cond = &cmd[1]; cond = &cmd[1];
else if (cmd[0] == ':') /* :ID */ else if (cmd[0] == ':') /* :ID */
id = &cmd[1]; id = &cmd[1];
else if (MATCH_CMD(cmd, "log", arg)) else if (is_opt(cmd, "log", ":"))
log = cmd; log = cmd;
else if (MATCH_CMD(cmd, "pid", arg)) else if (is_opt(cmd, "pid", ":"))
pid = cmd; pid = cmd;
else if (MATCH_CMD(cmd, "name:", arg)) else if (MATCH_CMD(cmd, "name:", arg))
name = cmd; name = cmd;
else if (MATCH_CMD(cmd, "notify:", arg)) else if (MATCH_CMD(cmd, "notify:", arg))
notify = arg; notify = arg;
else if (MATCH_CMD(cmd, "type:forking", arg)) else if (!strcasecmp(cmd, "type:forking"))
forking = 1; forking = 1;
else if (MATCH_CMD(cmd, "manual:yes", arg)) else if (!strcasecmp(cmd, "manual:yes"))
manual = 1; manual = 1;
else if (MATCH_CMD(cmd, "remain:yes", arg)) else if (!strcasecmp(cmd, "remain:yes"))
remain = 1; remain = 1;
else if (MATCH_CMD(cmd, "restart:", arg)) { else if (MATCH_CMD(cmd, "restart:", arg)) {
if (MATCH_CMD(arg, "always", arg)) if (MATCH_CMD(arg, "always", arg))
@@ -2310,9 +2325,9 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
restart_tmo = atoi(arg) * 1000; restart_tmo = atoi(arg) * 1000;
else if (MATCH_CMD(cmd, "restart_sec:", arg)) else if (MATCH_CMD(cmd, "restart_sec:", arg))
restart_tmo = atoi(arg) * 1000; restart_tmo = atoi(arg) * 1000;
else if (MATCH_CMD(cmd, "norestart", arg)) else if (!strcasecmp(cmd, "norestart"))
restart_max = 0; restart_max = 0;
else if (MATCH_CMD(cmd, "nowarn", arg)) else if (!strcasecmp(cmd, "nowarn"))
nowarn = 1; nowarn = 1;
else if (MATCH_CMD(cmd, "oncrash:", arg)) { else if (MATCH_CMD(cmd, "oncrash:", arg)) {
if (MATCH_CMD(arg, "reboot", arg)) if (MATCH_CMD(arg, "reboot", arg))
@@ -2320,7 +2335,7 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
if (MATCH_CMD(arg, "script", arg)) if (MATCH_CMD(arg, "script", arg))
oncrash_action = SVC_ONCRASH_SCRIPT; oncrash_action = SVC_ONCRASH_SCRIPT;
} }
else if (MATCH_CMD(cmd, "respawn", arg)) else if (!strcasecmp(cmd, "respawn"))
respawn = 1; respawn = 1;
else if (MATCH_CMD(cmd, "halt:", arg)) else if (MATCH_CMD(cmd, "halt:", arg))
halt = arg; halt = arg;
@@ -2345,7 +2360,7 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
else if (MATCH_CMD(cmd, "tty:", arg)) else if (MATCH_CMD(cmd, "tty:", arg))
ctty = arg; ctty = arg;
/* catch both cgroup: and cgroup. handled in parse_cgroup() */ /* catch both cgroup: and cgroup. handled in parse_cgroup() */
else if (MATCH_CMD(cmd, "cgroup", arg)) else if ((arg = is_opt(cmd, "cgroup", ".:")))
cgroup = arg; cgroup = arg;
else if (MATCH_CMD(cmd, "conflict:", arg)) else if (MATCH_CMD(cmd, "conflict:", arg))
conflict = arg; conflict = arg;
@@ -2730,6 +2745,45 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
return svc; return svc;
} }
/**
* service_register - Register from a legacy one-liner
* @type: %SVC_TYPE_SERVICE(0), %SVC_TYPE_TASK(1), %SVC_TYPE_RUN(2)
* @cfg: Configuration, complete command, with -- for description text
* @rlimit: Limits for this service/task/run, may be global limits
* @file: The file name service was loaded from
*
* The first "-- " in @cfg starts the description, the rest is handed
* to service_register_desc().
*/
svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
{
char *line, *desc;
if (!cfg)
return service_register_desc(type, cfg, NULL, rlimit, file);
line = strdupa(cfg);
desc = strstr(line, "-- ");
if (desc) {
*desc = 0;
desc += 3;
while (*desc && isblank(*desc))
desc++;
} else {
int pos;
/* Find "--\n" to denote empty/no description */
pos = (int)strlen(line) - 2;
if (pos > 0 && !strcmp(&line[pos], "--")) {
line[pos] = 0;
desc = &line[pos];
}
}
return service_register_desc(type, line, desc, rlimit, file);
}
/* /*
* This function is called at the end of a runlevel change or reload * This function is called at the end of a runlevel change or reload
* command to delete all remnants of removed services. * command to delete all remnants of removed services.
@@ -3378,8 +3432,8 @@ restart:
svc_set_state(svc, SVC_CLEANUP_STATE); svc_set_state(svc, SVC_CLEANUP_STATE);
service_cleanup_script(svc); service_cleanup_script(svc);
} else { } else {
/* Unblock remain tasks after post script so they can restart */ /* Restart remain tasks after runlevel/reload/restart teardown. */
if (svc_is_remain(svc)) if (svc_is_remain(svc) && svc_is_restart(svc))
svc_unblock(svc); svc_unblock(svc);
svc_set_state(svc, SVC_HALTED_STATE); svc_set_state(svc, SVC_HALTED_STATE);
} }
@@ -3398,13 +3452,17 @@ restart:
break; break;
case SVC_DONE_STATE: case SVC_DONE_STATE:
/* Remain tasks: always run post script when stopped, even if config changed */ /* Remain tasks run post script before stopping or restarting. */
if (svc_is_runtask(svc) && svc_is_remain(svc) && svc_is_stopped(svc)) { if (svc_is_runtask(svc) && svc_is_remain(svc) &&
(svc_is_stopped(svc) || svc_is_restart(svc))) {
if (svc_has_post(svc)) { if (svc_has_post(svc)) {
svc_set_state(svc, SVC_TEARDOWN_STATE); svc_set_state(svc, SVC_TEARDOWN_STATE);
service_post_script(svc); service_post_script(svc);
} else } else {
if (svc_is_restart(svc))
svc_unblock(svc);
svc_set_state(svc, SVC_HALTED_STATE); svc_set_state(svc, SVC_HALTED_STATE);
}
} else if (svc_is_changed(svc)) } else if (svc_is_changed(svc))
svc_set_state(svc, SVC_HALTED_STATE); svc_set_state(svc, SVC_HALTED_STATE);
else if (svc_is_runtask(svc) && svc_is_manual(svc) && enabled) else if (svc_is_runtask(svc) && svc_is_manual(svc) && enabled)
@@ -3727,7 +3785,7 @@ void service_runtask_clean(void)
/* Config changed or leaving runlevel: stop, run post, restart */ /* Config changed or leaving runlevel: stop, run post, restart */
svc->once = 0; svc->once = 0;
svc_stop(svc); svc_restarting(svc);
service_step(svc); service_step(svc);
continue; continue;
} }
+2
View File
@@ -44,6 +44,8 @@ extern const struct svcdir svcdirs[NUM_SVCDIRS];
int service_set_dir (svc_t *svc, const struct svcdir *sd, const char *name); int service_set_dir (svc_t *svc, const struct svcdir *sd, const char *name);
svc_t *service_register (int type, char *line, struct rlimit rlimit[], char *file); svc_t *service_register (int type, char *line, struct rlimit rlimit[], char *file);
svc_t *service_register_desc (int type, char *line, const char *desc,
struct rlimit rlimit[], char *file);
void service_unregister (svc_t *svc); void service_unregister (svc_t *svc);
void service_runtask_clean (void); void service_runtask_clean (void);
+2
View File
@@ -60,6 +60,7 @@ EXTRA_DIST += pre-fail.sh
EXTRA_DIST += process-depends.sh EXTRA_DIST += process-depends.sh
EXTRA_DIST += rclocal.sh EXTRA_DIST += rclocal.sh
EXTRA_DIST += ready-serv.sh EXTRA_DIST += ready-serv.sh
EXTRA_DIST += remain-task-control.sh
EXTRA_DIST += restart-self.sh EXTRA_DIST += restart-self.sh
EXTRA_DIST += runlevel.sh EXTRA_DIST += runlevel.sh
EXTRA_DIST += run-restart-forever.sh EXTRA_DIST += run-restart-forever.sh
@@ -129,6 +130,7 @@ TESTS += pre-fail.sh
TESTS += process-depends.sh TESTS += process-depends.sh
TESTS += rclocal.sh TESTS += rclocal.sh
TESTS += ready-serv.sh TESTS += ready-serv.sh
TESTS += remain-task-control.sh
TESTS += restart-self.sh TESTS += restart-self.sh
TESTS += runlevel.sh TESTS += runlevel.sh
TESTS += sysvparts.sh TESTS += sysvparts.sh
+24 -1
View File
@@ -76,6 +76,29 @@ retry 'assert_cmd pick "serv -np -i pick"'
assert_desc "Single command" pick assert_desc "Single command" pick
assert_num_children 1 serv assert_num_children 1 serv
# The description has its own setting, so a -- in the command is an
# argument to it, see issue #512.
say 'A -- in the command is passed on to it'
run "echo 'service pick {' > $FINIT_CONF"
run "echo ' description = \"Dashes\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i pick -- extra\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_cmd pick "serv -np -i pick -- extra"'
assert_desc "Dashes" pick
assert_num_children 1 serv
# Options are matched as whole tokens, so a command that merely starts
# like one, pidof vs. pid, is still the command.
say 'A command named like an option is the command'
run "echo 'task lookalike {' > $FINIT_CONF"
run "echo ' command = \"pidof serv\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_cmd lookalike "pidof serv"'
# A tty block takes the same list, its command is the getty to run. # A tty block takes the same list, its command is the getty to run.
# Asserting it is loaded is enough here, a getty in the test namespace # Asserting it is loaded is enough here, a getty in the test namespace
# has no terminal to attach to. # has no terminal to attach to.
@@ -90,7 +113,7 @@ retry 'assert_cmd tty "serv -np -i ttypick"'
# Nothing resolves, so the block is skipped the same way a single # Nothing resolves, so the block is skipped the same way a single
# missing command is. The leading - only decides whether that is # missing command is. The leading - only decides whether that is
# quiet, service_register() bails before svc_new() either way. # quiet, service_register_desc() bails before svc_new() either way.
say 'A block whose candidates are all missing is skipped' say 'A block whose candidates are all missing is skipped'
run "echo 'service ghost {' > $FINIT_CONF" run "echo 'service ghost {' > $FINIT_CONF"
run "echo ' command = { \"-/no/such/one\", \"-/no/such/two\" }' >> $FINIT_CONF" run "echo ' command = { \"-/no/such/one\", \"-/no/such/two\" }' >> $FINIT_CONF"
+76
View File
@@ -0,0 +1,76 @@
#!/bin/sh
set -eu
TEST_DIR=$(dirname "$0")
TEST_CONF=/etc/finit.d/remain-task.conf
STATE=/run/remain-task.state
test_teardown()
{
run "rm -f $TEST_CONF $STATE"
}
assert_runs()
{
assert "Remain task has run $1 time(s)" \
"$(texec initctl -j status remain-task | jq -M .restarts)" -eq "$1"
}
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
say 'Add remain task'
run "cat > $TEST_CONF" <<EOF
task remain-task {
description = "Remain task control"
runlevel = "2345"
remain-after-exit = true
command = "/bin/touch $STATE"
exec-stop-post = "/bin/rm -f $STATE"
}
EOF
run "initctl reload"
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
assert_runs 1
say 'Stop remain task'
run "initctl stop remain-task"
retry 'assert_status remain-task stopped'
assert "Post script removed state" \
"$(texec sh -c "[ ! -e '$STATE' ] && echo yes")" = yes
assert_runs 1
say 'Start remain task'
run "initctl start remain-task"
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
assert_runs 2
say 'Restart remain task'
run "initctl restart remain-task"
retry 'assert_runs 3'
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
say 'Reload changed remain task'
run "sed -i 's/Remain task control/Updated remain task control/' $TEST_CONF"
run "initctl reload"
retry 'assert_runs 4'
retry 'assert_status remain-task done'
assert_file_exists "$STATE"
say 'Leave remain task runlevel'
run "initctl runlevel 1"
retry 'assert_status remain-task halted'
assert "Post script removed state" \
"$(texec sh -c "[ ! -e '$STATE' ] && echo yes")" = yes
assert_runs 4
say 'Re-enter remain task runlevel'
run "initctl runlevel 2"
retry 'assert_runs 5'
retry 'assert_status remain-task done'
assert_file_exists "$STATE"