Merge pull request #12030 from poettering/condition-memory

add ConditionCPUs= + ConditionMemory=
This commit is contained in:
Yu Watanabe 2019-04-02 08:01:42 +09:00 committed by GitHub
commit 3f8f021541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 323 additions and 56 deletions

View File

@ -1002,6 +1002,8 @@
<term><varname>ConditionUser=</varname></term>
<term><varname>ConditionGroup=</varname></term>
<term><varname>ConditionControlGroupController=</varname></term>
<term><varname>ConditionMemory=</varname></term>
<term><varname>ConditionCPUs=</varname></term>
<!-- We do not document ConditionNull=
here, as it is not particularly
@ -1117,11 +1119,12 @@
the exact assignment is looked for with right and left hand
side matching.</para>
<para><varname>ConditionKernelVersion=</varname> may be used to check whether the kernel version (as reported
by <command>uname -r</command>) matches a certain expression (or if prefixed with the exclamation mark does not
match it). The argument must be a single string. If the string starts with one of <literal>&lt;</literal>,
<literal>&lt;=</literal>, <literal>=</literal>, <literal>&gt;=</literal>, <literal>&gt;</literal> a relative
version comparison is done, otherwise the specified string is matched with shell-style globs.</para>
<para><varname>ConditionKernelVersion=</varname> may be used to check whether the kernel version (as
reported by <command>uname -r</command>) matches a certain expression (or if prefixed with the
exclamation mark does not match it). The argument must be a single string. If the string starts with
one of <literal>&lt;</literal>, <literal>&lt;=</literal>, <literal>=</literal>,
<literal>!=</literal>, <literal>&gt;=</literal>, <literal>&gt;</literal> a relative version
comparison is done, otherwise the specified string is matched with shell-style globs.</para>
<para>Note that using the kernel version string is an unreliable way to determine which features are supported
by a kernel, because of the widespread practice of backporting drivers, features, and fixes from newer upstream
@ -1255,6 +1258,24 @@
<literal>blkio</literal>, <literal>memory</literal>,
<literal>devices</literal>, and <literal>pids</literal>.</para>
<para><varname>ConditionMemory=</varname> verifies if the specified amount of system memory is
available to the current system. Takes a memory size in bytes as argument, optionally prefixed with a
comparison operator <literal>&lt;</literal>, <literal>&lt;=</literal>, <literal>=</literal>,
<literal>!=</literal>, <literal>&gt;=</literal>, <literal>&gt;</literal>. On bare-metal systems
compares the amount of physical memory in the system with the specified size, adhering to the
specified comparison operator. In containers compares the amount of memory assigned to the container
instead.</para>
<para><varname>ConditionCPUs=</varname> verifies if the specified number of CPUs is available to the
current system. Takes a number of CPUs as argument, optionally prefixed with a comparison operator
<literal>&lt;</literal>, <literal>&lt;=</literal>, <literal>=</literal>, <literal>!=</literal>,
<literal>&gt;=</literal>, <literal>&gt;</literal>. Compares the number of CPUs in the CPU affinity mask
configured of the service manager itself with the specified number, adhering to the specified
comparision operator. On physical systems the number of CPUs in the affinity mask of the service
manager usually matches the number of physical CPUs, but in special and virtual environments might
differ. In particular, in containers the affinity mask usually matches the number of CPUs assigned to
the container and not the physically available ones.</para>
<para>If multiple conditions are specified, the unit will be
executed if all of them apply (i.e. a logical AND is applied).
Condition checks can be prefixed with a pipe symbol (|) in

View File

@ -1549,6 +1549,40 @@ int set_oom_score_adjust(int value) {
WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER);
}
int cpus_in_affinity_mask(void) {
size_t n = 16;
int r;
for (;;) {
cpu_set_t *c;
c = CPU_ALLOC(n);
if (!c)
return -ENOMEM;
if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) {
int k;
k = CPU_COUNT_S(CPU_ALLOC_SIZE(n), c);
CPU_FREE(c);
if (k <= 0)
return -EINVAL;
return k;
}
r = -errno;
CPU_FREE(c);
if (r != -EINVAL)
return r;
if (n > SIZE_MAX/2)
return -ENOMEM;
n *= 2;
}
}
static const char *const ioprio_class_table[] = {
[IOPRIO_CLASS_NONE] = "none",
[IOPRIO_CLASS_RT] = "realtime",

View File

@ -194,3 +194,5 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX)
(pid) = 0; \
_pid_; \
})
int cpus_in_affinity_mask(void);

View File

@ -22,16 +22,17 @@
#include "cgroup-util.h"
#include "condition.h"
#include "efivars.h"
#include "env-file.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
#include "glob-util.h"
#include "hostname-util.h"
#include "ima-util.h"
#include "limits-util.h"
#include "list.h"
#include "macro.h"
#include "mountpoint-util.h"
#include "env-file.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
@ -48,23 +49,25 @@
Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) {
Condition *c;
int r;
assert(type >= 0);
assert(type < _CONDITION_TYPE_MAX);
assert((!parameter) == (type == CONDITION_NULL));
c = new0(Condition, 1);
c = new(Condition, 1);
if (!c)
return NULL;
c->type = type;
c->trigger = trigger;
c->negate = negate;
*c = (Condition) {
.type = type,
.trigger = trigger,
.negate = negate,
};
r = free_and_strdup(&c->parameter, parameter);
if (r < 0) {
return mfree(c);
if (parameter) {
c->parameter = strdup(parameter);
if (!c->parameter)
return mfree(c);
}
return c;
@ -132,29 +135,77 @@ static int condition_test_kernel_command_line(Condition *c) {
return false;
}
static int condition_test_kernel_version(Condition *c) {
enum {
/* Listed in order of checking. Note that some comparators are prefixes of others, hence the longest
* should be listed first. */
LOWER_OR_EQUAL,
GREATER_OR_EQUAL,
LOWER,
GREATER,
EQUAL,
_ORDER_MAX,
};
typedef enum {
/* Listed in order of checking. Note that some comparators are prefixes of others, hence the longest
* should be listed first. */
ORDER_LOWER_OR_EQUAL,
ORDER_GREATER_OR_EQUAL,
ORDER_LOWER,
ORDER_GREATER,
ORDER_EQUAL,
ORDER_UNEQUAL,
_ORDER_MAX,
_ORDER_INVALID = -1
} OrderOperator;
static OrderOperator parse_order(const char **s) {
static const char *const prefix[_ORDER_MAX] = {
[LOWER_OR_EQUAL] = "<=",
[GREATER_OR_EQUAL] = ">=",
[LOWER] = "<",
[GREATER] = ">",
[EQUAL] = "=",
[ORDER_LOWER_OR_EQUAL] = "<=",
[ORDER_GREATER_OR_EQUAL] = ">=",
[ORDER_LOWER] = "<",
[ORDER_GREATER] = ">",
[ORDER_EQUAL] = "=",
[ORDER_UNEQUAL] = "!=",
};
const char *p = NULL;
OrderOperator i;
for (i = 0; i < _ORDER_MAX; i++) {
const char *e;
e = startswith(*s, prefix[i]);
if (e) {
*s = e;
return i;
}
}
return _ORDER_INVALID;
}
static bool test_order(int k, OrderOperator p) {
switch (p) {
case ORDER_LOWER:
return k < 0;
case ORDER_LOWER_OR_EQUAL:
return k <= 0;
case ORDER_EQUAL:
return k == 0;
case ORDER_UNEQUAL:
return k != 0;
case ORDER_GREATER_OR_EQUAL:
return k >= 0;
case ORDER_GREATER:
return k > 0;
default:
assert_not_reached("unknown order");
}
}
static int condition_test_kernel_version(Condition *c) {
OrderOperator order;
struct utsname u;
size_t i;
int k;
const char *p;
assert(c);
assert(c->parameter);
@ -162,38 +213,64 @@ static int condition_test_kernel_version(Condition *c) {
assert_se(uname(&u) >= 0);
for (i = 0; i < _ORDER_MAX; i++) {
p = startswith(c->parameter, prefix[i]);
if (p)
break;
}
p = c->parameter;
order = parse_order(&p);
/* No prefix? Then treat as glob string */
if (!p)
if (order < 0)
return fnmatch(skip_leading_chars(c->parameter, NULL), u.release, 0) == 0;
k = str_verscmp(u.release, skip_leading_chars(p, NULL));
return test_order(str_verscmp(u.release, skip_leading_chars(p, NULL)), order);
}
switch (i) {
static int condition_test_memory(Condition *c) {
OrderOperator order;
uint64_t m, k;
const char *p;
int r;
case LOWER:
return k < 0;
assert(c);
assert(c->parameter);
assert(c->type == CONDITION_MEMORY);
case LOWER_OR_EQUAL:
return k <= 0;
m = physical_memory();
case EQUAL:
return k == 0;
p = c->parameter;
order = parse_order(&p);
if (order < 0)
order = ORDER_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
case GREATER_OR_EQUAL:
return k >= 0;
r = safe_atou64(p, &k);
if (r < 0)
return log_debug_errno(r, "Failed to parse size: %m");
case GREATER:
return k > 0;
return test_order(CMP(m, k), order);
}
default:
assert_not_reached("Can't compare");
}
static int condition_test_cpus(Condition *c) {
OrderOperator order;
const char *p;
unsigned k;
int r, n;
assert(c);
assert(c->parameter);
assert(c->type == CONDITION_CPUS);
n = cpus_in_affinity_mask();
if (n < 0)
return log_debug_errno(n, "Failed to determine CPUs in affinity mask: %m");
p = c->parameter;
order = parse_order(&p);
if (order < 0)
order = ORDER_GREATER_OR_EQUAL; /* default to >= check, if nothing is specified. */
r = safe_atou(p, &k);
if (r < 0)
return log_debug_errno(r, "Failed to parse number of CPUs: %m");
return test_order(CMP((unsigned) n, k), order);
}
static int condition_test_user(Condition *c) {
@ -629,6 +706,8 @@ int condition_test(Condition *c) {
[CONDITION_GROUP] = condition_test_group,
[CONDITION_CONTROL_GROUP_CONTROLLER] = condition_test_control_group_controller,
[CONDITION_NULL] = condition_test_null,
[CONDITION_CPUS] = condition_test_cpus,
[CONDITION_MEMORY] = condition_test_memory,
};
int r, b;
@ -740,7 +819,9 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_USER] = "ConditionUser",
[CONDITION_GROUP] = "ConditionGroup",
[CONDITION_CONTROL_GROUP_CONTROLLER] = "ConditionControlGroupController",
[CONDITION_NULL] = "ConditionNull"
[CONDITION_NULL] = "ConditionNull",
[CONDITION_CPUS] = "ConditionCPUs",
[CONDITION_MEMORY] = "ConditionMemory",
};
DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
@ -768,7 +849,9 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_USER] = "AssertUser",
[CONDITION_GROUP] = "AssertGroup",
[CONDITION_CONTROL_GROUP_CONTROLLER] = "AssertControlGroupController",
[CONDITION_NULL] = "AssertNull"
[CONDITION_NULL] = "AssertNull",
[CONDITION_CPUS] = "AssertCPUs",
[CONDITION_MEMORY] = "AssertMemory",
};
DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);

View File

@ -16,6 +16,8 @@ typedef enum ConditionType {
CONDITION_SECURITY,
CONDITION_CAPABILITY,
CONDITION_AC_POWER,
CONDITION_MEMORY,
CONDITION_CPUS,
CONDITION_NEEDS_UPDATE,
CONDITION_FIRST_BOOT,

View File

@ -17,9 +17,11 @@
#include "hostname-util.h"
#include "id128-util.h"
#include "ima-util.h"
#include "limits-util.h"
#include "log.h"
#include "macro.h"
#include "nulstr-util.h"
#include "process-util.h"
#include "selinux-util.h"
#include "set.h"
#include "smack-util.h"
@ -673,6 +675,127 @@ static void test_condition_test_group(void) {
condition_free(condition);
}
static void test_condition_test_cpus_one(const char *s, bool result) {
Condition *condition;
int r;
log_debug("%s=%s", condition_type_to_string(CONDITION_CPUS), s);
condition = condition_new(CONDITION_CPUS, s, false, false);
assert_se(condition);
r = condition_test(condition);
assert_se(r >= 0);
assert_se(r == result);
condition_free(condition);
}
static void test_condition_test_cpus(void) {
_cleanup_free_ char *t = NULL;
int cpus;
cpus = cpus_in_affinity_mask();
assert_se(cpus >= 0);
test_condition_test_cpus_one("> 0", true);
test_condition_test_cpus_one(">= 0", true);
test_condition_test_cpus_one("!= 0", true);
test_condition_test_cpus_one("<= 0", false);
test_condition_test_cpus_one("< 0", false);
test_condition_test_cpus_one("= 0", false);
test_condition_test_cpus_one("> 100000", false);
test_condition_test_cpus_one("= 100000", false);
test_condition_test_cpus_one(">= 100000", false);
test_condition_test_cpus_one("< 100000", true);
test_condition_test_cpus_one("!= 100000", true);
test_condition_test_cpus_one("<= 100000", true);
assert_se(asprintf(&t, "= %i", cpus) >= 0);
test_condition_test_cpus_one(t, true);
t = mfree(t);
assert_se(asprintf(&t, "<= %i", cpus) >= 0);
test_condition_test_cpus_one(t, true);
t = mfree(t);
assert_se(asprintf(&t, ">= %i", cpus) >= 0);
test_condition_test_cpus_one(t, true);
t = mfree(t);
assert_se(asprintf(&t, "!= %i", cpus) >= 0);
test_condition_test_cpus_one(t, false);
t = mfree(t);
assert_se(asprintf(&t, "< %i", cpus) >= 0);
test_condition_test_cpus_one(t, false);
t = mfree(t);
assert_se(asprintf(&t, "> %i", cpus) >= 0);
test_condition_test_cpus_one(t, false);
t = mfree(t);
}
static void test_condition_test_memory_one(const char *s, bool result) {
Condition *condition;
int r;
log_debug("%s=%s", condition_type_to_string(CONDITION_MEMORY), s);
condition = condition_new(CONDITION_MEMORY, s, false, false);
assert_se(condition);
r = condition_test(condition);
assert_se(r >= 0);
assert_se(r == result);
condition_free(condition);
}
static void test_condition_test_memory(void) {
_cleanup_free_ char *t = NULL;
uint64_t memory;
memory = physical_memory();
test_condition_test_memory_one("> 0", true);
test_condition_test_memory_one(">= 0", true);
test_condition_test_memory_one("!= 0", true);
test_condition_test_memory_one("<= 0", false);
test_condition_test_memory_one("< 0", false);
test_condition_test_memory_one("= 0", false);
test_condition_test_memory_one("> 18446744073709547520", false);
test_condition_test_memory_one("= 18446744073709547520", false);
test_condition_test_memory_one(">= 18446744073709547520", false);
test_condition_test_memory_one("< 18446744073709547520", true);
test_condition_test_memory_one("!= 18446744073709547520", true);
test_condition_test_memory_one("<= 18446744073709547520", true);
assert_se(asprintf(&t, "= %" PRIu64, memory) >= 0);
test_condition_test_memory_one(t, true);
t = mfree(t);
assert_se(asprintf(&t, "<= %" PRIu64, memory) >= 0);
test_condition_test_memory_one(t, true);
t = mfree(t);
assert_se(asprintf(&t, ">= %" PRIu64, memory) >= 0);
test_condition_test_memory_one(t, true);
t = mfree(t);
assert_se(asprintf(&t, "!= %" PRIu64, memory) >= 0);
test_condition_test_memory_one(t, false);
t = mfree(t);
assert_se(asprintf(&t, "< %" PRIu64, memory) >= 0);
test_condition_test_memory_one(t, false);
t = mfree(t);
assert_se(asprintf(&t, "> %" PRIu64, memory) >= 0);
test_condition_test_memory_one(t, false);
t = mfree(t);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
@ -689,6 +812,8 @@ int main(int argc, char *argv[]) {
test_condition_test_user();
test_condition_test_group();
test_condition_test_control_group_controller();
test_condition_test_cpus();
test_condition_test_memory();
return 0;
}