From: Lennart Poettering Date: Fri, 21 Feb 2014 01:06:04 +0000 (+0100) Subject: core: add new ConditionArchitecture() that checks the architecture returned by uname... X-Git-Tag: v210~103 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=099524d7b0df690e3361ffc3fe3c6aed0558b4fc core: add new ConditionArchitecture() that checks the architecture returned by uname()'s machine field. --- diff --git a/Makefile.am b/Makefile.am index ac90c1890..85d51b4a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -674,6 +674,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/util.h \ src/shared/virt.c \ src/shared/virt.h \ + src/shared/architecture.c \ + src/shared/architecture.h \ src/shared/efivars.c \ src/shared/efivars.h \ src/shared/path-util.c \ @@ -1150,7 +1152,8 @@ tests += \ test-list \ test-tables \ test-device-nodes \ - test-xml + test-xml \ + test-architecture EXTRA_DIST += \ test/sched_idle_bad.service \ @@ -1298,6 +1301,12 @@ test_time_SOURCES = \ test_time_LDADD = \ libsystemd-core.la +test_architecture_SOURCES = \ + src/test/test-architecture.c + +test_architecture_LDADD = \ + libsystemd-shared.la + test_log_SOURCES = \ src/test/test-log.c diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 2a965f5a1..2b59b1b84 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -879,6 +879,13 @@ + ConditionArchitecture= + ConditionVirtualization= + ConditionHost= + ConditionKernelCommandLine= + ConditionSecurity= + ConditionCapability= + ConditionACPower= ConditionPathExists= ConditionPathExistsGlob= ConditionPathIsDirectory= @@ -888,12 +895,6 @@ ConditionDirectoryNotEmpty= ConditionFileNotEmpty= ConditionFileIsExecutable= - ConditionKernelCommandLine= - ConditionVirtualization= - ConditionSecurity= - ConditionCapability= - ConditionHost= - ConditionACPower= ConditionNull= Before starting a unit @@ -908,93 +909,46 @@ queued start job is to be executed. - With - ConditionPathExists= - a file existence condition is - checked before a unit is started. If - the specified absolute path name does - not exist, the condition will - fail. If the absolute path name passed - to - ConditionPathExists= - is prefixed with an exclamation mark - (!), the test is negated, and the unit - is only started if the path does not - exist. - - ConditionPathExistsGlob= - is similar to - ConditionPathExists=, - but checks for the existence of at - least one file or directory matching - the specified globbing pattern. - - ConditionPathIsDirectory= - is similar to - ConditionPathExists= - but verifies whether a certain path - exists and is a - directory. - - ConditionPathIsSymbolicLink= - is similar to - ConditionPathExists= - but verifies whether a certain path - exists and is a symbolic - link. - - ConditionPathIsMountPoint= - is similar to - ConditionPathExists= - but verifies whether a certain path - exists and is a mount - point. - - ConditionPathIsReadWrite= - is similar to - ConditionPathExists= - but verifies whether the underlying - file system is readable and writable - (i.e. not mounted - read-only). - - ConditionDirectoryNotEmpty= - is similar to - ConditionPathExists= - but verifies whether a certain path - exists and is a non-empty - directory. - - ConditionFileNotEmpty= - is similar to - ConditionPathExists= - but verifies whether a certain path - exists and refers to a regular file - with a non-zero size. - - ConditionFileIsExecutable= - is similar to - ConditionPathExists= - but verifies whether a certain path - exists, is a regular file and marked - executable. - - Similarly, - ConditionKernelCommandLine= - may be used to check whether a - specific kernel command line option is - set (or if prefixed with the - exclamation mark unset). The argument - must either be a single word, or an - assignment (i.e. two words, separated - =). In the former - case the kernel command line is - searched for the word appearing as is, - or as left hand side of an - assignment. In the latter case the - exact assignment is looked for with - right and left hand side - matching. + ConditionArchitecture= + may be used to check whether the + system is running on a specific + architecture. Takes one of + x86, + x86-64, + ppc, + ppc64, + ia64, + parisc, + parisc64, + s390, + s390x, + sparc, + sparc64, + mips, + mips64, + alpha, + arm, + arm-be, + arm64, + arm64-be, + sh, + sh64, + m86k to test + against a specific architecture. The + architecture is determined from the + information returned by + uname2 + and is thus subject to + personality2. Note + that a Personality= + setting in the same unit file has no + effect on this condition. A special + architecture name + native is mapped to + the architecture the system manager + itself is compiled for. The test may + be negated by prepending an + exclamation mark. ConditionVirtualization= may be used to check whether the @@ -1028,6 +982,36 @@ test may be negated by prepending an exclamation mark. + ConditionHost= + may be used to match against the + hostname or machine ID of the + host. This either takes a hostname + string (optionally with shell style + globs) which is tested against the + locally set hostname as returned by + gethostname2, + or a machine ID formatted as string + (see + machine-id5). + The test may be negated by prepending + an exclamation mark. + + ConditionKernelCommandLine= + may be used to check whether a + specific kernel command line option is + set (or if prefixed with the + exclamation mark unset). The argument + must either be a single word, or an + assignment (i.e. two words, separated + =). In the former + case the kernel command line is + searched for the word appearing as is, + or as left hand side of an + assignment. In the latter case the + exact assignment is looked for with + right and left hand side + matching. + ConditionSecurity= may be used to check whether the given security module is enabled on the @@ -1053,20 +1037,6 @@ possibly prefixed with an exclamation mark to negate the check. - ConditionHost= - may be used to match against the - hostname or machine ID of the - host. This either takes a hostname - string (optionally with shell style - globs) which is tested against the - locally set hostname as returned by - gethostname2, - or a machine ID formatted as string - (see - machine-id5). - The test may be negated by prepending - an exclamation mark. - ConditionACPower= may be used to check whether the system has AC power, or is exclusively @@ -1084,6 +1054,77 @@ all AC connectors are disconnected from a power source. + With + ConditionPathExists= + a file existence condition is + checked before a unit is started. If + the specified absolute path name does + not exist, the condition will + fail. If the absolute path name passed + to + ConditionPathExists= + is prefixed with an exclamation mark + (!), the test is negated, and the unit + is only started if the path does not + exist. + + ConditionPathExistsGlob= + is similar to + ConditionPathExists=, + but checks for the existence of at + least one file or directory matching + the specified globbing pattern. + + ConditionPathIsDirectory= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a + directory. + + ConditionPathIsSymbolicLink= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a symbolic + link. + + ConditionPathIsMountPoint= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a mount + point. + + ConditionPathIsReadWrite= + is similar to + ConditionPathExists= + but verifies whether the underlying + file system is readable and writable + (i.e. not mounted + read-only). + + ConditionDirectoryNotEmpty= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and is a non-empty + directory. + + ConditionFileNotEmpty= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists and refers to a regular file + with a non-zero size. + + ConditionFileIsExecutable= + is similar to + ConditionPathExists= + but verifies whether a certain path + exists, is a regular file and marked + executable. + Finally, ConditionNull= may be used to add a constant condition diff --git a/src/core/condition.c b/src/core/condition.c index 658e8d6a4..1448fa19d 100644 --- a/src/core/condition.c +++ b/src/core/condition.c @@ -167,6 +167,9 @@ static bool condition_test(Condition *c) { case CONDITION_AC_POWER: return condition_test_ac_power(c); + case CONDITION_ARCHITECTURE: + return condition_test_architecture(c); + case CONDITION_NULL: return !c->negate; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 1c2c142ee..e9995bf0c 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -153,6 +153,7 @@ Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_D Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, 0 Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, 0 Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, 0 +Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, 0 Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, 0 Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, 0 Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, 0 diff --git a/src/shared/architecture.c b/src/shared/architecture.c new file mode 100644 index 000000000..1dc5fa5c0 --- /dev/null +++ b/src/shared/architecture.c @@ -0,0 +1,159 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "architecture.h" + +Architecture uname_architecture(void) { + + /* Return a sanitized enum identifying the architecture we are + * running on. This is based on uname(), and the user may + * hence control what this returns by using + * personality(). This puts the user in control on systems + * that can run binaries of multiple architectures. + * + * We do not translate the string returned by uname() + * 1:1. Instead we try to clean it up and break down the + * confusion on x86 and arm in particular. + * + * We do not try to distuingish CPUs not CPU features, but + * actual architectures, i.e. that have genuinely different + * code. */ + + static const struct { + const char *machine; + Architecture arch; + } arch_map[] = { +#if defined(__x86_64__) || defined(__i386__) + { "x86_64", ARCHITECTURE_X86_64 }, + { "i686", ARCHITECTURE_X86 }, + { "i586", ARCHITECTURE_X86 }, + { "i486", ARCHITECTURE_X86 }, + { "i386", ARCHITECTURE_X86 }, +#elif defined(__powerpc__) || defined(__powerpc64__) + { "ppc64", ARCHITECTURE_PPC64 }, + { "ppc", ARCHITECTURE_PPC }, +#elif defined(__ia64__) + { "ia64", ARCHITECTURE_IA64 }, +#elif defined(__hppa__) || defined(__hppa64__) + { "parisc64", ARCHITECTURE_PARISC64 }, + { "parisc", ARCHITECTURE_PARISC }, +#elif defined(__s390__) || defined(__s390x__) + { "s390x", ARCHITECTURE_S390X }, + { "s390", ARCHITECTURE_S390 }, +#elif defined(__sparc__) || defined(__sparc64__) + { "sparc64", ARCHITECTURE_SPARC64 }, + { "sparc", ARCHITECTURE_SPARC }, +#elif defined(__mips__) || defined(__mips64__) + { "mips64", ARCHITECTURE_MIPS64 }, + { "mips", ARCHITECTURE_MIPS }, +#elif defined(__alpha__) + { "alpha" , ARCHITECTURE_ALPHA }, +#elif defined(__arm__) || defined(__aarch64__) + { "aarch64", ARCHITECTURE_ARM64 }, + { "aarch64_be", ARCHITECTURE_ARM64_BE }, + { "armv4l", ARCHITECTURE_ARM }, + { "armv4b", ARCHITECTURE_ARM_BE }, + { "armv4tl", ARCHITECTURE_ARM }, + { "armv4tb", ARCHITECTURE_ARM_BE }, + { "armv5tl", ARCHITECTURE_ARM }, + { "armv5tb", ARCHITECTURE_ARM_BE }, + { "armv5tel", ARCHITECTURE_ARM }, + { "armv5teb" , ARCHITECTURE_ARM_BE }, + { "armv5tejl", ARCHITECTURE_ARM }, + { "armv5tejb", ARCHITECTURE_ARM_BE }, + { "armv6l", ARCHITECTURE_ARM }, + { "armv6b", ARCHITECTURE_ARM_BE }, + { "armv7l", ARCHITECTURE_ARM }, + { "armv7b", ARCHITECTURE_ARM_BE }, + { "armv7ml", ARCHITECTURE_ARM }, + { "armv7mb", ARCHITECTURE_ARM_BE }, + { "armv4l", ARCHITECTURE_ARM }, + { "armv4b", ARCHITECTURE_ARM_BE }, + { "armv4tl", ARCHITECTURE_ARM }, + { "armv4tb", ARCHITECTURE_ARM_BE }, + { "armv5tl", ARCHITECTURE_ARM }, + { "armv5tb", ARCHITECTURE_ARM_BE }, + { "armv5tel", ARCHITECTURE_ARM }, + { "armv5teb", ARCHITECTURE_ARM_BE }, + { "armv5tejl", ARCHITECTURE_ARM }, + { "armv5tejb", ARCHITECTURE_ARM_BE }, + { "armv6l", ARCHITECTURE_ARM }, + { "armv6b", ARCHITECTURE_ARM_BE }, + { "armv7l", ARCHITECTURE_ARM }, + { "armv7b", ARCHITECTURE_ARM_BE }, + { "armv7ml", ARCHITECTURE_ARM }, + { "armv7mb", ARCHITECTURE_ARM_BE }, + { "armv8l", ARCHITECTURE_ARM }, + { "armv8b", ARCHITECTURE_ARM_BE }, +#elif defined(__sh__) || defined(__sh64__) + { "sh64", ARCHITECTURE_SH64 }, + { "sh", ARCHITECTURE_SH }, +#elif defined(__m68k__) + { "m68k", ARCHITECTURE_M68K }, +#else +#error "Please register your architecture here!" +#endif + }; + + static Architecture cached = _ARCHITECTURE_INVALID; + struct utsname u; + unsigned i; + + if (cached != _ARCHITECTURE_INVALID) + return cached; + + assert_se(uname(&u) >= 0); + + for (i = 0; i < ELEMENTSOF(arch_map); i++) + if (streq(arch_map[i].machine, u.machine)) + return cached = arch_map[i].arch; + + assert_not_reached("Couldn't identify architecture. You need to patch systemd."); + return _ARCHITECTURE_INVALID; +} + +static const char *const architecture_table[_ARCHITECTURE_MAX] = { + [ARCHITECTURE_X86] = "x86", + [ARCHITECTURE_X86_64] = "x86-64", + [ARCHITECTURE_PPC] = "ppc", + [ARCHITECTURE_PPC64] = "ppc64", + [ARCHITECTURE_IA64] = "ia64", + [ARCHITECTURE_PARISC] = "parisc", + [ARCHITECTURE_PARISC64] = "parisc64", + [ARCHITECTURE_S390] = "s390", + [ARCHITECTURE_S390X] = "s390x", + [ARCHITECTURE_SPARC] = "sparc", + [ARCHITECTURE_SPARC64] = "sparc64", + [ARCHITECTURE_MIPS] = "mips", + [ARCHITECTURE_MIPS64] = "mips64", + [ARCHITECTURE_ALPHA] = "alpha", + [ARCHITECTURE_ARM] = "arm", + [ARCHITECTURE_ARM_BE] = "arm-be", + [ARCHITECTURE_ARM64] = "arm64", + [ARCHITECTURE_ARM64_BE] = "arm64-be", + [ARCHITECTURE_SH] = "sh", + [ARCHITECTURE_SH64] = "sh64", + [ARCHITECTURE_M68K] = "m68k", +}; + +DEFINE_STRING_TABLE_LOOKUP(architecture, Architecture); diff --git a/src/shared/architecture.h b/src/shared/architecture.h new file mode 100644 index 000000000..fcfc45aa7 --- /dev/null +++ b/src/shared/architecture.h @@ -0,0 +1,105 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" + +typedef enum Architecture { + ARCHITECTURE_X86 = 0, + ARCHITECTURE_X86_64, + ARCHITECTURE_PPC, + ARCHITECTURE_PPC64, + ARCHITECTURE_IA64, + ARCHITECTURE_PARISC, + ARCHITECTURE_PARISC64, + ARCHITECTURE_S390, + ARCHITECTURE_S390X, + ARCHITECTURE_SPARC, + ARCHITECTURE_SPARC64, + ARCHITECTURE_MIPS, + ARCHITECTURE_MIPS64, + ARCHITECTURE_ALPHA, + ARCHITECTURE_ARM, + ARCHITECTURE_ARM_BE, + ARCHITECTURE_ARM64, + ARCHITECTURE_ARM64_BE, + ARCHITECTURE_SH, + ARCHITECTURE_SH64, + ARCHITECTURE_M68K, + _ARCHITECTURE_MAX, + _ARCHITECTURE_INVALID = -1 +} Architecture; + +Architecture uname_architecture(void); + +#if defined(__x86_64__) +# define native_architecture() ARCHITECTURE_X86_64 +#elif defined(__i386__) +# define native_architecture() ARCHITECTURE_X86 +#elif defined(__powerpc64__) +# define native_architecture() ARCHITECTURE_PPC64 +#elif defined(__powerpc__) +# define native_architecture() ARCHITECTURE_PPC +#elif defined(__ia64__) +# define native_architecture() ARCHITECTURE_IA64 +#elif defined(__hppa64__) +# define native_architecture() ARCHITECTURE_PARISC64 +#elif defined(__hppa__) +# define native_architecture() ARCHITECTURE_PARISC +#elif defined(__s390x__) +# define native_architecture() ARCHITECTURE_S390X +#elif defined(__s390__) +# define native_architecture() ARCHITECTURE_S390 +#elif defined(__sparc64__) +# define native_architecture() ARCHITECTURE_SPARC64 +#elif defined(__sparc__) +# define native_architecture() ARCHITECTURE_SPARC +#elif defined(__mips64__) +# define native_architecture() ARCHITECTURE_MIPS64 +#elif defined(__mips__) +# define native_architecture() ARCHITECTURE_MIPS +#elif defined(__alpha__) +# define native_architecture() ARCHITECTURE_ALPHA +#elif defined(__aarch64__) +# ifdef defined(WORDS_BIGENDIAN) +# define native_architecture() ARCHITECTURE_ARM64_BE +# else +# define native_architecture() ARCHITECTURE_ARM64 +# endif +#elif defined(__arm__) +# ifdef defined(WORDS_BIGENDIAN) +# define native_architecture() ARCHITECTURE_ARM_BE +# else +# define native_architecture() ARCHITECTURE_ARM +# endif +#elif defined(__sh64__) +# define native_architecture() ARCHITECTURE_SH64 +#elif defined(__sh__) +# define native_architecture() ARCHITECTURE_SH +#elif defined(__m68k__) +# define native_architecture() ARCHITECTURE_M68K +#else +#error "Please register your architecture here!" +#endif + +const char *architecture_to_string(Architecture a) _const_; +Architecture architecture_from_string(const char *s) _pure_; diff --git a/src/shared/condition-util.c b/src/shared/condition-util.c index d31c4bf5b..4aea3caf9 100644 --- a/src/shared/condition-util.c +++ b/src/shared/condition-util.c @@ -33,6 +33,7 @@ #include "path-util.h" #include "fileio.h" #include "unit.h" +#include "architecture.h" Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { Condition *c; @@ -154,6 +155,28 @@ bool condition_test_virtualization(Condition *c) { return (v > 0 && streq(c->parameter, id)) == !c->negate; } +bool condition_test_architecture(Condition *c) { + Architecture a, b; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_ARCHITECTURE); + + a = uname_architecture(); + if (a < 0) + return c->negate; + + if (streq(c->parameter, "native")) + b = native_architecture(); + else + b = architecture_from_string(c->parameter); + + if (b < 0) + return c->negate; + + return (a == b) == !c->negate; +} + bool condition_test_host(Condition *c) { sd_id128_t x, y; char *h; @@ -170,7 +193,7 @@ bool condition_test_host(Condition *c) { if (r < 0) return c->negate; - return sd_id128_equal(x, y); + return sd_id128_equal(x, y) == !c->negate; } h = gethostname_malloc(); @@ -237,6 +260,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_CAPABILITY] = "ConditionCapability", [CONDITION_HOST] = "ConditionHost", [CONDITION_AC_POWER] = "ConditionACPower", + [CONDITION_ARCHITECTURE] = "ConditionArchitecture", [CONDITION_NULL] = "ConditionNull" }; diff --git a/src/shared/condition-util.h b/src/shared/condition-util.h index 85d4d5bbf..63d945efa 100644 --- a/src/shared/condition-util.h +++ b/src/shared/condition-util.h @@ -43,6 +43,7 @@ typedef enum ConditionType { CONDITION_CAPABILITY, CONDITION_HOST, CONDITION_AC_POWER, + CONDITION_ARCHITECTURE, CONDITION_NULL, _CONDITION_TYPE_MAX, _CONDITION_TYPE_INVALID = -1 @@ -67,6 +68,7 @@ void condition_free_list(Condition *c); bool condition_test_kernel_command_line(Condition *c); bool condition_test_virtualization(Condition *c); +bool condition_test_architecture(Condition *c); bool condition_test_host(Condition *c); bool condition_test_ac_power(Condition *c); diff --git a/src/test/test-architecture.c b/src/test/test-architecture.c new file mode 100644 index 000000000..b586c0da5 --- /dev/null +++ b/src/test/test-architecture.c @@ -0,0 +1,52 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "virt.h" +#include "architecture.h" +#include "util.h" +#include "log.h" + +int main(int argc, char *argv[]) { + Architecture a; + Virtualization v; + const char *id = NULL; + + v = detect_virtualization(&id); + if (v == -EPERM || v == -EACCES) + return EXIT_TEST_SKIP; + + assert_se(v >= 0); + + log_info("virtualization=%s id=%s", + v == VIRTUALIZATION_CONTAINER ? "container" : v == VIRTUALIZATION_VM ? "vm" : "n/a", + strna(id)); + + a = uname_architecture(); + assert_se(a >= 0); + + log_info("uname architecture=%s", architecture_to_string(a)); + + a = native_architecture(); + assert_se(a >= 0); + + log_info("native architecture=%s", architecture_to_string(a)); + return 0; +}