introduce a new "bad-setting" unit load state in order to improve "systemctl status" output when bad settings are used
<listitem><para>These options take a whitespace-separated list of directory names. The specified directory
names must be relative, and may not include <literal>..</literal>. If set, one or more
- directories by the specified names will be created (including their parents) below <filename>/run</filename>
- (or <varname>$XDG_RUNTIME_DIR</varname> for user services), <filename>/var/lib</filename> (or
- <varname>$XDG_CONFIG_HOME</varname> for user services), <filename>/var/cache</filename> (or
- <varname>$XDG_CACHE_HOME</varname> for user services), <filename>/var/log</filename> (or
- <varname>$XDG_CONFIG_HOME</varname><filename>/log</filename> for user services), or <filename>/etc</filename>
- (or <varname>$XDG_CONFIG_HOME</varname> for user services), respectively, when the unit is started.</para>
-
+ directories by the specified names will be created (including their parents) below the locations
+ defined in the following table, when the unit is started.</para>
+ <table>
+ <title>Automatic directory creation</title>
+ <tgroup cols='3'>
+ <thead>
+ <row>
+ <entry>Locations</entry>
+ <entry>for system</entry>
+ <entry>for users</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><varname>RuntimeDirectory=</varname></entry>
+ <entry><filename>/run</filename></entry>
+ <entry><varname>$XDG_RUNTIME_DIR</varname></entry>
+ </row>
+ <row>
+ <entry><varname>StateDirectory=</varname></entry>
+ <entry><filename>/var/lib</filename></entry>
+ <entry><varname>$XDG_CONFIG_HOME</varname></entry>
+ </row>
+ <row>
+ <entry><varname>CacheDirectory=</varname></entry>
+ <entry><filename>/var/cache</filename></entry>
+ <entry><varname>$XDG_CACHE_HOME</varname></entry>
+ </row>
+ <row>
+ <entry><varname>LogsDirectory=</varname></entry>
+ <entry><filename>/var/log</filename></entry>
+ <entry><varname>$XDG_CONFIG_HOME</varname><filename>/log</filename></entry>
+ </row>
+ <row>
+ <entry><varname>ConfigurationDirectory=</varname></entry>
+ <entry><filename>/etc</filename></entry>
+ <entry><varname>$XDG_CONFIG_HOME</varname></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<para>In case of <varname>RuntimeDirectory=</varname> the lowest subdirectories are removed when the unit is
stopped. It is possible to preserve the specified directories in this case if
<varname>RuntimeDirectoryPreserve=</varname> is configured to <option>restart</option> or <option>yes</option>
</varlistentry>
</variablelist>
</refsect1>
+
+ <refsect1>
+ <title>[CAN] Section Options</title>
+ <para>The <literal>[CAN]</literal> section manages the Controller Area Network (CAN bus) and accepts the
+ following keys.</para>
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>BitRate=</varname></term>
+ <listitem>
+ <para>The bitrate of CAN device in bits per second. The usual SI prefixes (K, M) with the base of 1000 can
+ be used here.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>SamplePoint=</varname></term>
+ <listitem>
+ <para>Optional sample point in percent with one decimal (e.g. <literal>75%</literal>,
+ <literal>87.5%</literal>) or permille (e.g. <literal>875‰</literal>).</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>RestartSec=</varname></term>
+ <listitem>
+ <para>Automatic restart delay time. If set to a non-zero value, a restart of the CAN controller will be
+ triggered automatically in case of a bus-off condition after the specified delay time. Subsecond delays can
+ be specified using decimals (e.g. <literal>0.1s</literal>) or a <literal>ms</literal> or
+ <literal>us</literal> postfix. Using <literal>infinity</literal> or <literal>0</literal> will turn the
+ automatic restart off. By default automatic restart is disabled.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>[BridgeVLAN] Section Options</title>
<para>The <literal>[BridgeVLAN]</literal> section manages the VLAN ID configuration of a bridge port and accepts
return v;
}
+int parse_permille_unbounded(const char *p) {
+ const char *pc, *pm, *dot, *n;
+ int r, q, v;
+
+ pm = endswith(p, "‰");
+ if (pm) {
+ n = strndupa(p, pm - p);
+ r = safe_atoi(n, &v);
+ if (r < 0)
+ return r;
+ } else {
+ pc = endswith(p, "%");
+ if (!pc)
+ return -EINVAL;
+
+ dot = memchr(p, '.', pc - p);
+ if (dot) {
+ if (dot + 2 != pc)
+ return -EINVAL;
+ if (dot[1] < '0' || dot[1] > '9')
+ return -EINVAL;
+ q = dot[1] - '0';
+ n = strndupa(p, dot - p);
+ } else {
+ q = 0;
+ n = strndupa(p, pc - p);
+ }
+ r = safe_atoi(n, &v);
+ if (r < 0)
+ return r;
+ if (v > ((INT_MAX - q) / 10))
+ return -ERANGE;
+
+ v = v * 10 + q;
+ }
+
+ if (v < 0)
+ return -ERANGE;
+
+ return v;
+}
+
+int parse_permille(const char *p) {
+ int v;
+
+ v = parse_permille_unbounded(p);
+ if (v > 1000)
+ return -ERANGE;
+
+ return v;
+}
+
int parse_nice(const char *p, int *ret) {
int n, r;
int parse_percent_unbounded(const char *p);
int parse_percent(const char *p);
+int parse_permille_unbounded(const char *p);
+int parse_permille(const char *p);
+
int parse_nice(const char *p, int *ret);
int parse_ip_port(const char *s, uint16_t *ret);
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Socket, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("NConnections", "u", bus_property_get_unsigned, offsetof(Socket, n_connections), 0),
SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0),
+ SD_BUS_PROPERTY("NRefused", "u", bus_property_get_unsigned, offsetof(Socket, n_refused), 0),
SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0),
SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TriggerLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, trigger_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST),
log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled.");
if (cfd >= 0)
- cfd = safe_close(cfd);
+ goto refuse;
else
flush_ports(s);
}
if (!ratelimit_below(&s->trigger_limit)) {
- safe_close(cfd);
log_unit_warning(UNIT(s), "Trigger limit hit, refusing further activation.");
socket_enter_stop_pre(s, SOCKET_FAILURE_TRIGGER_LIMIT_HIT);
- return;
+ goto refuse;
}
if (cfd < 0) {
if (s->n_connections >= s->max_connections) {
log_unit_warning(UNIT(s), "Too many incoming connections (%u), dropping connection.",
s->n_connections);
- safe_close(cfd);
- return;
+ goto refuse;
}
if (s->max_connections_per_source > 0) {
r = socket_acquire_peer(s, cfd, &p);
if (r < 0) {
- safe_close(cfd);
- return;
+ goto refuse;
} else if (r > 0 && p->n_ref > s->max_connections_per_source) {
_cleanup_free_ char *t = NULL;
log_unit_warning(UNIT(s),
"Too many incoming connections (%u) from source %s, dropping connection.",
p->n_ref, strnull(t));
- safe_close(cfd);
- return;
+ goto refuse;
}
}
/* ENOTCONN is legitimate if TCP RST was received.
* This connection is over, but the socket unit lives on. */
log_unit_debug(UNIT(s), "Got ENOTCONN on incoming socket, assuming aborted connection attempt, ignoring.");
- safe_close(cfd);
- return;
+ goto refuse;
}
r = unit_name_to_prefix(UNIT(s)->id, &prefix);
return;
+refuse:
+ s->n_refused++;
+ safe_close(cfd);
+ return;
+
fail:
log_unit_warning(UNIT(s), "Failed to queue service startup job (Maybe the service file is missing or not a %s unit?): %s",
cfd >= 0 ? "template" : "non-template",
unit_serialize_item(u, f, "state", socket_state_to_string(s->state));
unit_serialize_item(u, f, "result", socket_result_to_string(s->result));
unit_serialize_item_format(u, f, "n-accepted", "%u", s->n_accepted);
+ unit_serialize_item_format(u, f, "n-refused", "%u", s->n_refused);
if (s->control_pid > 0)
unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid);
log_unit_debug(u, "Failed to parse n-accepted value: %s", value);
else
s->n_accepted += k;
+ } else if (streq(key, "n-refused")) {
+ unsigned k;
+
+ if (safe_atou(value, &k) < 0)
+ log_unit_debug(u, "Failed to parse n-refused value: %s", value);
+ else
+ s->n_refused += k;
} else if (streq(key, "control-pid")) {
pid_t pid;
unsigned n_accepted;
unsigned n_connections;
+ unsigned n_refused;
unsigned max_connections;
unsigned max_connections_per_source;
[IFLA_GENEVE_LABEL] = { .type = NETLINK_TYPE_U32 },
};
+static const NLType rtnl_link_info_data_can_types[] = {
+ [IFLA_CAN_BITTIMING] = { .size = sizeof(struct can_bittiming) },
+ [IFLA_CAN_RESTART_MS] = { .type = NETLINK_TYPE_U32 },
+};
+
/* these strings must match the .kind entries in the kernel */
static const char* const nl_union_link_info_data_table[] = {
[NL_UNION_LINK_INFO_DATA_BOND] = "bond",
[NL_UNION_LINK_INFO_DATA_VXCAN] = "vxcan",
[NL_UNION_LINK_INFO_DATA_WIREGUARD] = "wireguard",
[NL_UNION_LINK_INFO_DATA_NETDEVSIM] = "netdevsim",
+ [NL_UNION_LINK_INFO_DATA_CAN] = "can",
};
DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData);
.types = rtnl_link_info_data_geneve_types },
[NL_UNION_LINK_INFO_DATA_VXCAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxcan_types),
.types = rtnl_link_info_data_vxcan_types },
+ [NL_UNION_LINK_INFO_DATA_CAN] = { .count = ELEMENTSOF(rtnl_link_info_data_can_types),
+ .types = rtnl_link_info_data_can_types },
};
static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = {
NL_UNION_LINK_INFO_DATA_VXCAN,
NL_UNION_LINK_INFO_DATA_WIREGUARD,
NL_UNION_LINK_INFO_DATA_NETDEVSIM,
+ NL_UNION_LINK_INFO_DATA_CAN,
_NL_UNION_LINK_INFO_DATA_MAX,
_NL_UNION_LINK_INFO_DATA_INVALID = -1
} NLUnionLinkInfoData;
#include <netinet/ether.h>
#include <linux/if.h>
+#include <linux/can/netlink.h>
#include <unistd.h>
#include <stdio_ext.h>
return 0;
}
-static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
- _cleanup_(link_unrefp) Link *link = userdata;
+static int link_up_can(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
assert(link);
- if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
- return 1;
+ log_link_debug(link, "Bringing CAN link up");
- r = sd_netlink_message_get_errno(m);
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
if (r < 0)
- log_link_warning_errno(link, r, "Could not bring down interface: %m");
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
- return 1;
+ r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set link flags: %m");
+
+ r = sd_netlink_call_async(link->manager->rtnl, req, link_up_handler, link, 0, NULL);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
}
-int link_down(Link *link) {
- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+static int link_set_can(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
int r;
assert(link);
+ assert(link->network);
assert(link->manager);
assert(link->manager->rtnl);
- log_link_debug(link, "Bringing link down");
+ log_link_debug(link, "link_set_can");
- r = sd_rtnl_message_new_link(link->manager->rtnl, &req,
- RTM_SETLINK, link->ifindex);
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->ifindex);
if (r < 0)
- return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+ return log_link_error_errno(link, r, "Failed to allocate netlink message: %m");
- r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP);
+ r = sd_netlink_message_set_flags(m, NLM_F_REQUEST | NLM_F_ACK);
if (r < 0)
- return log_link_error_errno(link, r, "Could not set link flags: %m");
+ return log_link_error_errno(link, r, "Could not set netlink flags: %m");
- r = sd_netlink_call_async(link->manager->rtnl, req, link_down_handler, link, 0, NULL);
+ r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to open netlink container: %m");
+
+ r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, link->kind);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ if (link->network->can_bitrate > 0 || link->network->can_sample_point > 0) {
+ struct can_bittiming bt = {
+ .bitrate = link->network->can_bitrate,
+ .sample_point = link->network->can_sample_point,
+ };
+
+ if (link->network->can_bitrate > UINT32_MAX) {
+ log_link_error(link, "bitrate (%zu) too big.", link->network->can_bitrate);
+ return -ERANGE;
+ }
+
+ log_link_debug(link, "Setting bitrate = %d bit/s", bt.bitrate);
+ if (link->network->can_sample_point > 0)
+ log_link_debug(link, "Setting sample point = %d.%d%%", bt.sample_point / 10, bt.sample_point % 10);
+ else
+ log_link_debug(link, "Using default sample point");
+
+ r = sd_netlink_message_append_data(m, IFLA_CAN_BITTIMING, &bt, sizeof(bt));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_CAN_BITTIMING attribute: %m");
+ }
+
+ if (link->network->can_restart_us > 0) {
+ char time_string[FORMAT_TIMESPAN_MAX];
+ uint64_t restart_ms;
+
+ if (link->network->can_restart_us == USEC_INFINITY)
+ restart_ms = 0;
+ else
+ restart_ms = DIV_ROUND_UP(link->network->can_restart_us, USEC_PER_MSEC);
+
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, restart_ms * 1000, MSEC_PER_SEC);
+
+ if (restart_ms > UINT32_MAX) {
+ log_link_error(link, "restart timeout (%s) too big.", time_string);
+ return -ERANGE;
+ }
+
+ log_link_debug(link, "Setting restart = %s", time_string);
+
+ r = sd_netlink_message_append_u32(m, IFLA_CAN_RESTART_MS, restart_ms);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_CAN_RESTART_MS attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to close netlink container: %m");
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to close netlink container: %m");
+
+ r = sd_netlink_call_async(link->manager->rtnl, m, link_set_handler, link, 0, NULL);
if (r < 0)
return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
link_ref(link);
- return 0;
+ if (!(link->flags & IFF_UP)) {
+ r = link_up_can(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+ }
+
+ log_link_debug(link, "link_set_can done");
+
+ return r;
}
-static int link_up_can(Link *link) {
+static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
+ _cleanup_(link_unrefp) Link *link = userdata;
+ int r;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not bring down interface: %m");
+
+ if (streq_ptr(link->kind, "can")) {
+ link_ref(link);
+ link_set_can(link);
+ }
+
+ return 1;
+}
+
+int link_down(Link *link) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
- log_link_debug(link, "Bringing CAN link up");
+ log_link_debug(link, "Bringing link down");
- r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req,
+ RTM_SETLINK, link->ifindex);
if (r < 0)
return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
- r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP);
+ r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP);
if (r < 0)
return log_link_error_errno(link, r, "Could not set link flags: %m");
- r = sd_netlink_call_async(link->manager->rtnl, req, link_up_handler, link, 0, NULL);
+ r = sd_netlink_call_async(link->manager->rtnl, req, link_down_handler, link, 0, NULL);
if (r < 0)
return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
return r;
}
-static int link_configure(Link *link) {
+static int link_configure_can(Link *link) {
int r;
- assert(link);
- assert(link->network);
- assert(link->state == LINK_STATE_PENDING);
-
- if (streq_ptr(link->kind, "vcan")) {
-
- if (!(link->flags & IFF_UP)) {
- r = link_up_can(link);
+ if (streq_ptr(link->kind, "can")) {
+ /* The CAN interface must be down to configure bitrate, etc... */
+ if ((link->flags & IFF_UP)) {
+ r = link_down(link);
if (r < 0) {
link_enter_failed(link);
return r;
}
+
+ return 0;
}
- return 0;
+ return link_set_can(link);
+ }
+
+ if (!(link->flags & IFF_UP)) {
+ r = link_up_can(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
}
+ return 0;
+}
+
+static int link_configure(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state == LINK_STATE_PENDING);
+
+ if (STRPTR_IN_SET(link->kind, "can", "vcan"))
+ return link_configure_can(link);
+
/* Drop foreign config, but ignore loopback or critical devices.
* We do not want to remove loopback address or addresses used for root NFS. */
if (!(link->flags & IFF_LOOPBACK) && !(link->network->dhcp_critical)) {
IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0
IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0
+CAN.BitRate, config_parse_si_size, 0, offsetof(Network, can_bitrate)
+CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
+CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
"BridgeFDB\0"
"BridgeVLAN\0"
"IPv6PrefixDelegation\0"
- "IPv6Prefix\0",
+ "IPv6Prefix\0"
+ "CAN\0",
config_item_perf_lookup, network_network_gperf_lookup,
CONFIG_PARSE_WARN, network);
if (r < 0)
uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN];
uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN];
+ /* CAN support */
+ size_t can_bitrate;
+ unsigned can_sample_point;
+ usec_t can_restart_us;
+
AddressFamilyBoolean ip_forward;
bool ip_masquerade;
return 0;
}
+
+int config_parse_permille(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ unsigned *permille = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(permille);
+
+ r = parse_permille(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse permille value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *permille = (unsigned) r;
+
+ return 0;
+}
CONFIG_PARSER_PROTOTYPE(config_parse_log_level);
CONFIG_PARSER_PROTOTYPE(config_parse_signal);
CONFIG_PARSER_PROTOTYPE(config_parse_personality);
+CONFIG_PARSER_PROTOTYPE(config_parse_permille);
CONFIG_PARSER_PROTOTYPE(config_parse_ifname);
CONFIG_PARSER_PROTOTYPE(config_parse_ip_port);
CONFIG_PARSER_PROTOTYPE(config_parse_join_controllers);
/* Socket */
unsigned n_accepted;
unsigned n_connections;
+ unsigned n_refused;
bool accept;
/* Pairs of type, path */
STRV_FOREACH_PAIR(t, t2, i->listen)
printf(" %*s %s (%s)\n", 9, t == i->listen ? "Listen:" : "", *t2, *t);
- if (i->accept)
- printf(" Accepted: %u; Connected: %u\n", i->n_accepted, i->n_connections);
+ if (i->accept) {
+ printf(" Accepted: %u; Connected: %u;", i->n_accepted, i->n_connections);
+ if (i->n_refused)
+ printf(" Refused: %u", i->n_refused);
+ printf("\n");
+ }
+
LIST_FOREACH(exec, p, i->exec) {
_cleanup_free_ char *argv = NULL;
{ "NextElapseUSecMonotonic", "t", NULL, offsetof(UnitStatusInfo, next_elapse_monotonic) },
{ "NAccepted", "u", NULL, offsetof(UnitStatusInfo, n_accepted) },
{ "NConnections", "u", NULL, offsetof(UnitStatusInfo, n_connections) },
+ { "NRefused", "u", NULL, offsetof(UnitStatusInfo, n_refused) },
{ "Accept", "b", NULL, offsetof(UnitStatusInfo, accept) },
{ "Listen", "a(ss)", map_listen, offsetof(UnitStatusInfo, listen) },
{ "SysFSPath", "s", NULL, offsetof(UnitStatusInfo, sysfs_path) },
assert_se(parse_percent_unbounded("400%") == 400);
}
+static void test_parse_permille(void) {
+ assert_se(parse_permille("") == -EINVAL);
+ assert_se(parse_permille("foo") == -EINVAL);
+ assert_se(parse_permille("0") == -EINVAL);
+ assert_se(parse_permille("50") == -EINVAL);
+ assert_se(parse_permille("100") == -EINVAL);
+ assert_se(parse_permille("-1") == -EINVAL);
+
+ assert_se(parse_permille("0‰") == 0);
+ assert_se(parse_permille("555‰") == 555);
+ assert_se(parse_permille("1000‰") == 1000);
+ assert_se(parse_permille("-7‰") == -ERANGE);
+ assert_se(parse_permille("1007‰") == -ERANGE);
+ assert_se(parse_permille("‰") == -EINVAL);
+ assert_se(parse_permille("‰‰") == -EINVAL);
+ assert_se(parse_permille("‰1") == -EINVAL);
+ assert_se(parse_permille("1‰‰") == -EINVAL);
+ assert_se(parse_permille("3.2‰") == -EINVAL);
+
+ assert_se(parse_permille("0%") == 0);
+ assert_se(parse_permille("55%") == 550);
+ assert_se(parse_permille("55.5%") == 555);
+ assert_se(parse_permille("100%") == 1000);
+ assert_se(parse_permille("-7%") == -ERANGE);
+ assert_se(parse_permille("107%") == -ERANGE);
+ assert_se(parse_permille("%") == -EINVAL);
+ assert_se(parse_permille("%%") == -EINVAL);
+ assert_se(parse_permille("%1") == -EINVAL);
+ assert_se(parse_permille("1%%") == -EINVAL);
+ assert_se(parse_permille("3.21%") == -EINVAL);
+}
+
+static void test_parse_permille_unbounded(void) {
+ assert_se(parse_permille_unbounded("1001‰") == 1001);
+ assert_se(parse_permille_unbounded("4000‰") == 4000);
+ assert_se(parse_permille_unbounded("2147483647‰") == 2147483647);
+ assert_se(parse_permille_unbounded("2147483648‰") == -ERANGE);
+ assert_se(parse_permille_unbounded("4294967295‰") == -ERANGE);
+ assert_se(parse_permille_unbounded("4294967296‰") == -ERANGE);
+
+ assert_se(parse_permille_unbounded("101%") == 1010);
+ assert_se(parse_permille_unbounded("400%") == 4000);
+ assert_se(parse_permille_unbounded("214748364.7%") == 2147483647);
+ assert_se(parse_permille_unbounded("214748364.8%") == -ERANGE);
+ assert_se(parse_permille_unbounded("429496729.5%") == -ERANGE);
+ assert_se(parse_permille_unbounded("429496729.6%") == -ERANGE);
+}
+
static void test_parse_nice(void) {
int n;
test_safe_atod();
test_parse_percent();
test_parse_percent_unbounded();
+ test_parse_permille();
+ test_parse_permille_unbounded();
test_parse_nice();
test_parse_dev();
test_parse_errno();